1use std::{
19 collections::{BTreeMap, HashMap},
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 }
119}
120
121impl<ItemId, Item, Gap> RelationalLinkedChunk<ItemId, Item, Gap>
122where
123 Item: IndexableItem<ItemId = ItemId>,
124 ItemId: Hash + PartialEq + Eq + Clone + Ord,
125{
126 pub fn new() -> Self {
128 Self { chunks: Vec::new(), items_chunks: Vec::new(), items: HashMap::new() }
129 }
130
131 pub fn clear(&mut self) {
133 self.chunks.clear();
134 self.items_chunks.clear();
135 self.items.clear();
136 }
137
138 pub fn apply_updates(
141 &mut self,
142 linked_chunk_id: LinkedChunkId<'_>,
143 updates: Vec<Update<Item, Gap>>,
144 ) -> Result<(), RelationalLinkedChunkError> {
145 for update in updates {
146 match update {
147 Update::NewItemsChunk { previous, new, next } => {
148 Self::insert_chunk(&mut self.chunks, linked_chunk_id, previous, new, next)?;
149 }
150
151 Update::NewGapChunk { previous, new, next, gap } => {
152 Self::insert_chunk(&mut self.chunks, linked_chunk_id, previous, new, next)?;
153 self.items_chunks.push(ItemRow {
154 linked_chunk_id: linked_chunk_id.to_owned(),
155 position: Position::new(new, 0),
156 item: Either::Gap(gap),
157 });
158 }
159
160 Update::RemoveChunk(chunk_identifier) => {
161 Self::remove_chunk(&mut self.chunks, linked_chunk_id, chunk_identifier);
162
163 let indices_to_remove = self
164 .items_chunks
165 .iter()
166 .enumerate()
167 .filter_map(
168 |(
169 nth,
170 ItemRow {
171 linked_chunk_id: linked_chunk_id_candidate,
172 position,
173 ..
174 },
175 )| {
176 (linked_chunk_id == linked_chunk_id_candidate
177 && position.chunk_identifier() == chunk_identifier)
178 .then_some(nth)
179 },
180 )
181 .collect::<Vec<_>>();
182
183 for index_to_remove in indices_to_remove.into_iter().rev() {
184 self.items_chunks.remove(index_to_remove);
185 }
186 }
187
188 Update::PushItems { mut at, items } => {
189 for item in items {
190 let item_id = item.id();
191 self.items
192 .entry(linked_chunk_id.to_owned())
193 .or_default()
194 .insert(item_id.clone(), (item, Some(at)));
195 self.items_chunks.push(ItemRow {
196 linked_chunk_id: linked_chunk_id.to_owned(),
197 position: at,
198 item: Either::Item(item_id),
199 });
200 at.increment_index();
201 }
202 }
203
204 Update::ReplaceItem { at, item } => {
205 let existing = self
206 .items_chunks
207 .iter_mut()
208 .find(|item| item.position == at)
209 .expect("trying to replace at an unknown position");
210 assert!(
211 matches!(existing.item, Either::Item(..)),
212 "trying to replace a gap with an item"
213 );
214 let item_id = item.id();
215 self.items
216 .entry(linked_chunk_id.to_owned())
217 .or_default()
218 .insert(item_id.clone(), (item, Some(at)));
219 existing.item = Either::Item(item_id);
220 }
221
222 Update::RemoveItem { at } => {
223 let mut entry_to_remove = None;
224
225 for (
226 nth,
227 ItemRow { linked_chunk_id: linked_chunk_id_candidate, position, .. },
228 ) in self.items_chunks.iter_mut().enumerate()
229 {
230 if linked_chunk_id != &*linked_chunk_id_candidate {
232 continue;
233 }
234
235 if *position == at {
237 debug_assert!(entry_to_remove.is_none(), "Found the same entry twice");
238
239 entry_to_remove = Some(nth);
240 }
241
242 if position.chunk_identifier() == at.chunk_identifier()
244 && position.index() > at.index()
245 {
246 position.decrement_index();
247 }
248 }
249
250 self.items_chunks.remove(entry_to_remove.expect("Remove an unknown item"));
251 }
253
254 Update::DetachLastItems { at } => {
255 let indices_to_remove = self
256 .items_chunks
257 .iter()
258 .enumerate()
259 .filter_map(
260 |(
261 nth,
262 ItemRow {
263 linked_chunk_id: linked_chunk_id_candidate,
264 position,
265 ..
266 },
267 )| {
268 (linked_chunk_id == linked_chunk_id_candidate
269 && position.chunk_identifier() == at.chunk_identifier()
270 && position.index() >= at.index())
271 .then_some(nth)
272 },
273 )
274 .collect::<Vec<_>>();
275
276 for index_to_remove in indices_to_remove.into_iter().rev() {
277 self.items_chunks.remove(index_to_remove);
278 }
279 }
280
281 Update::StartReattachItems | Update::EndReattachItems => { }
282
283 Update::Clear => {
284 self.chunks.retain(|chunk| chunk.linked_chunk_id != linked_chunk_id);
285 self.items_chunks.retain(|chunk| chunk.linked_chunk_id != linked_chunk_id);
286 }
288 }
289 }
290 Ok(())
291 }
292
293 fn insert_chunk(
294 chunks: &mut Vec<ChunkRow>,
295 linked_chunk_id: LinkedChunkId<'_>,
296 previous: Option<ChunkIdentifier>,
297 new: ChunkIdentifier,
298 next: Option<ChunkIdentifier>,
299 ) -> Result<(), RelationalLinkedChunkError> {
300 if let Some(previous) = previous {
302 let entry_for_previous_chunk = chunks
303 .iter_mut()
304 .find(|ChunkRow { linked_chunk_id: linked_chunk_id_candidate, chunk, .. }| {
305 linked_chunk_id == linked_chunk_id_candidate && *chunk == previous
306 })
307 .ok_or(RelationalLinkedChunkError::InvalidChunkIdentifier {
308 identifier: previous,
309 })?;
310
311 entry_for_previous_chunk.next_chunk = Some(new);
313 }
314
315 if let Some(next) = next {
317 let entry_for_next_chunk = chunks
318 .iter_mut()
319 .find(|ChunkRow { linked_chunk_id: linked_chunk_id_candidate, chunk, .. }| {
320 linked_chunk_id == linked_chunk_id_candidate && *chunk == next
321 })
322 .ok_or(RelationalLinkedChunkError::InvalidChunkIdentifier { identifier: next })?;
323
324 entry_for_next_chunk.previous_chunk = Some(new);
326 }
327
328 chunks.push(ChunkRow {
330 linked_chunk_id: linked_chunk_id.to_owned(),
331 previous_chunk: previous,
332 chunk: new,
333 next_chunk: next,
334 });
335
336 Ok(())
337 }
338
339 fn remove_chunk(
340 chunks: &mut Vec<ChunkRow>,
341 linked_chunk_id: LinkedChunkId<'_>,
342 chunk_to_remove: ChunkIdentifier,
343 ) {
344 let entry_nth_to_remove = chunks
345 .iter()
346 .enumerate()
347 .find_map(
348 |(nth, ChunkRow { linked_chunk_id: linked_chunk_id_candidate, chunk, .. })| {
349 (linked_chunk_id == linked_chunk_id_candidate && *chunk == chunk_to_remove)
350 .then_some(nth)
351 },
352 )
353 .expect("Remove an unknown chunk");
354
355 let ChunkRow { linked_chunk_id, previous_chunk: previous, next_chunk: next, .. } =
356 chunks.remove(entry_nth_to_remove);
357
358 if let Some(previous) = previous {
360 let entry_for_previous_chunk = chunks
361 .iter_mut()
362 .find(|ChunkRow { linked_chunk_id: linked_chunk_id_candidate, chunk, .. }| {
363 &linked_chunk_id == linked_chunk_id_candidate && *chunk == previous
364 })
365 .expect("Previous chunk should be present");
366
367 entry_for_previous_chunk.next_chunk = next;
369 }
370
371 if let Some(next) = next {
373 let entry_for_next_chunk = chunks
374 .iter_mut()
375 .find(|ChunkRow { linked_chunk_id: linked_chunk_id_candidate, chunk, .. }| {
376 &linked_chunk_id == linked_chunk_id_candidate && *chunk == next
377 })
378 .expect("Next chunk should be present");
379
380 entry_for_next_chunk.previous_chunk = previous;
382 }
383 }
384
385 pub fn unordered_linked_chunk_items<'a>(
388 &'a self,
389 target: &OwnedLinkedChunkId,
390 ) -> impl Iterator<Item = (&'a Item, Position)> + use<'a, ItemId, Item, Gap> {
391 self.items.get(target).into_iter().flat_map(|items| {
392 items.values().filter_map(|(item, pos)| pos.map(|pos| (item, pos)))
394 })
395 }
396
397 pub fn items<'a>(
405 &'a self,
406 room_id: &'a RoomId,
407 ) -> impl Iterator<Item = (&'a Item, Option<Position>)> {
408 self.items
409 .iter()
410 .filter_map(move |(linked_chunk_id, items)| {
411 (linked_chunk_id.room_id() == room_id).then_some(items)
412 })
413 .flat_map(|items| items.values().map(|(item, pos)| (item, *pos)))
414 }
415
416 pub fn save_item(&mut self, room_id: OwnedRoomId, item: Item) {
418 let id = item.id();
419 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id);
420
421 let map = self.items.entry(linked_chunk_id).or_default();
422 if let Some(prev_value) = map.get_mut(&id) {
423 prev_value.0 = item;
425 } else {
426 map.insert(id, (item, None));
427 }
428 }
429}
430
431impl<ItemId, Item, Gap> RelationalLinkedChunk<ItemId, Item, Gap>
432where
433 Gap: Clone,
434 Item: Clone,
435 ItemId: Hash + PartialEq + Eq + Ord,
436{
437 #[doc(hidden)]
442 pub fn load_all_chunks(
443 &self,
444 linked_chunk_id: LinkedChunkId<'_>,
445 ) -> Result<Vec<RawChunk<Item, Gap>>, String> {
446 self.chunks
447 .iter()
448 .filter(|chunk| chunk.linked_chunk_id == linked_chunk_id)
449 .map(|chunk_row| load_raw_chunk(self, chunk_row, linked_chunk_id))
450 .collect::<Result<Vec<_>, String>>()
451 }
452
453 #[doc(hidden)]
458 pub fn load_all_chunks_metadata(
459 &self,
460 linked_chunk_id: LinkedChunkId<'_>,
461 ) -> Result<Vec<ChunkMetadata>, String> {
462 self.chunks
463 .iter()
464 .filter(|chunk| chunk.linked_chunk_id == linked_chunk_id)
465 .map(|chunk_row| load_raw_chunk_metadata(self, chunk_row, linked_chunk_id))
466 .collect::<Result<Vec<_>, String>>()
467 }
468
469 pub fn load_last_chunk(
470 &self,
471 linked_chunk_id: LinkedChunkId<'_>,
472 ) -> Result<(Option<RawChunk<Item, Gap>>, ChunkIdentifierGenerator), String> {
473 let chunk_identifier_generator = match self
475 .chunks
476 .iter()
477 .filter_map(|chunk_row| {
478 (chunk_row.linked_chunk_id == linked_chunk_id).then_some(chunk_row.chunk)
479 })
480 .max()
481 {
482 Some(last_chunk_identifier) => {
483 ChunkIdentifierGenerator::new_from_previous_chunk_identifier(last_chunk_identifier)
484 }
485 None => ChunkIdentifierGenerator::new_from_scratch(),
486 };
487
488 let mut number_of_chunks = 0;
490 let mut chunk_row = None;
491
492 for chunk_row_candidate in &self.chunks {
493 if chunk_row_candidate.linked_chunk_id == linked_chunk_id {
494 number_of_chunks += 1;
495
496 if chunk_row_candidate.next_chunk.is_none() {
497 chunk_row = Some(chunk_row_candidate);
498
499 break;
500 }
501 }
502 }
503
504 let chunk_row = match chunk_row {
505 Some(chunk_row) => chunk_row,
507
508 None if number_of_chunks == 0 => {
511 return Ok((None, chunk_identifier_generator));
512 }
513
514 None => {
519 return Err(
520 "last chunk is not found but chunks exist: the linked chunk contains a cycle"
521 .to_owned(),
522 );
523 }
524 };
525
526 load_raw_chunk(self, chunk_row, linked_chunk_id)
528 .map(|raw_chunk| (Some(raw_chunk), chunk_identifier_generator))
529 }
530
531 pub fn load_previous_chunk(
532 &self,
533 linked_chunk_id: LinkedChunkId<'_>,
534 before_chunk_identifier: ChunkIdentifier,
535 ) -> Result<Option<RawChunk<Item, Gap>>, String> {
536 let Some(chunk_row) = self.chunks.iter().find(|chunk_row| {
538 chunk_row.linked_chunk_id == linked_chunk_id
539 && chunk_row.next_chunk == Some(before_chunk_identifier)
540 }) else {
541 return Ok(None);
543 };
544
545 load_raw_chunk(self, chunk_row, linked_chunk_id).map(Some)
547 }
548}
549
550impl<ItemId, Item, Gap> Default for RelationalLinkedChunk<ItemId, Item, Gap>
551where
552 Item: IndexableItem<ItemId = ItemId>,
553 ItemId: Hash + PartialEq + Eq + Clone + Ord,
554{
555 fn default() -> Self {
556 Self::new()
557 }
558}
559
560fn load_raw_chunk<ItemId, Item, Gap>(
565 relational_linked_chunk: &RelationalLinkedChunk<ItemId, Item, Gap>,
566 chunk_row: &ChunkRow,
567 linked_chunk_id: LinkedChunkId<'_>,
568) -> Result<RawChunk<Item, Gap>, String>
569where
570 Item: Clone,
571 Gap: Clone,
572 ItemId: Hash + PartialEq + Eq + Ord,
573{
574 let mut items = relational_linked_chunk
576 .items_chunks
577 .iter()
578 .filter(|item_row| {
579 item_row.linked_chunk_id == linked_chunk_id
580 && item_row.position.chunk_identifier() == chunk_row.chunk
581 })
582 .peekable();
583
584 let Some(first_item) = items.peek() else {
585 return Ok(RawChunk {
587 content: ChunkContent::Items(Vec::new()),
588 previous: chunk_row.previous_chunk,
589 identifier: chunk_row.chunk,
590 next: chunk_row.next_chunk,
591 });
592 };
593
594 Ok(match first_item.item {
595 Either::Item(_) => {
597 let mut collected_items = Vec::new();
599
600 for item_row in items {
601 match &item_row.item {
602 Either::Item(item_id) => {
603 collected_items.push((item_id, item_row.position.index()))
604 }
605
606 Either::Gap(_) => {
607 return Err(format!(
608 "unexpected gap in items chunk {}",
609 chunk_row.chunk.index()
610 ));
611 }
612 }
613 }
614
615 collected_items.sort_unstable_by_key(|(_item, index)| *index);
617
618 RawChunk {
619 content: ChunkContent::Items(
620 collected_items
621 .into_iter()
622 .filter_map(|(item_id, _index)| {
623 Some(
624 relational_linked_chunk
625 .items
626 .get(&linked_chunk_id.to_owned())?
627 .get(item_id)?
628 .0
629 .clone(),
630 )
631 })
632 .collect(),
633 ),
634 previous: chunk_row.previous_chunk,
635 identifier: chunk_row.chunk,
636 next: chunk_row.next_chunk,
637 }
638 }
639
640 Either::Gap(ref gap) => {
641 assert!(items.next().is_some(), "we just peeked the gap");
642
643 if items.next().is_some() {
645 return Err(format!(
646 "there shouldn't be more than one item row attached in gap chunk {}",
647 chunk_row.chunk.index()
648 ));
649 }
650
651 RawChunk {
652 content: ChunkContent::Gap(gap.clone()),
653 previous: chunk_row.previous_chunk,
654 identifier: chunk_row.chunk,
655 next: chunk_row.next_chunk,
656 }
657 }
658 })
659}
660
661fn load_raw_chunk_metadata<ItemId, Item, Gap>(
666 relational_linked_chunk: &RelationalLinkedChunk<ItemId, Item, Gap>,
667 chunk_row: &ChunkRow,
668 linked_chunk_id: LinkedChunkId<'_>,
669) -> Result<ChunkMetadata, String>
670where
671 Item: Clone,
672 Gap: Clone,
673 ItemId: Hash + PartialEq + Eq,
674{
675 let mut items = relational_linked_chunk
677 .items_chunks
678 .iter()
679 .filter(|item_row| {
680 item_row.linked_chunk_id == linked_chunk_id
681 && item_row.position.chunk_identifier() == chunk_row.chunk
682 })
683 .peekable();
684
685 let Some(first_item) = items.peek() else {
686 return Ok(ChunkMetadata {
688 num_items: 0,
689 previous: chunk_row.previous_chunk,
690 identifier: chunk_row.chunk,
691 next: chunk_row.next_chunk,
692 });
693 };
694
695 Ok(match first_item.item {
696 Either::Item(_) => {
698 let mut num_items = 0;
702 for item in items {
703 match &item.item {
704 Either::Item(_) => num_items += 1,
705 Either::Gap(_) => {
706 return Err(format!(
707 "unexpected gap in items chunk {}",
708 chunk_row.chunk.index()
709 ));
710 }
711 }
712 }
713
714 ChunkMetadata {
715 num_items,
716 previous: chunk_row.previous_chunk,
717 identifier: chunk_row.chunk,
718 next: chunk_row.next_chunk,
719 }
720 }
721
722 Either::Gap(..) => {
723 assert!(items.next().is_some(), "we just peeked the gap");
724
725 if items.next().is_some() {
727 return Err(format!(
728 "there shouldn't be more than one item row attached in gap chunk {}",
729 chunk_row.chunk.index()
730 ));
731 }
732
733 ChunkMetadata {
734 num_items: 0,
736 previous: chunk_row.previous_chunk,
737 identifier: chunk_row.chunk,
738 next: chunk_row.next_chunk,
739 }
740 }
741 })
742}
743
744#[cfg(test)]
745mod tests {
746 use std::collections::BTreeMap;
747
748 use assert_matches::assert_matches;
749 use ruma::room_id;
750
751 use super::{super::lazy_loader::from_all_chunks, ChunkIdentifier as CId, *};
752
753 impl IndexableItem for char {
754 type ItemId = char;
755
756 fn id(&self) -> Self::ItemId {
757 *self
758 }
759 }
760
761 #[test]
762 fn test_new_items_chunk() {
763 let room_id = room_id!("!r0:matrix.org");
764 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
765
766 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
767
768 relational_linked_chunk
769 .apply_updates(
770 linked_chunk_id.as_ref(),
771 vec![
772 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
774 Update::NewItemsChunk {
776 previous: Some(CId::new(0)),
777 new: CId::new(1),
778 next: None,
779 },
780 Update::NewItemsChunk {
782 previous: None,
783 new: CId::new(2),
784 next: Some(CId::new(0)),
785 },
786 Update::NewItemsChunk {
788 previous: Some(CId::new(2)),
789 new: CId::new(3),
790 next: Some(CId::new(0)),
791 },
792 ],
793 )
794 .unwrap();
795
796 assert_eq!(
798 relational_linked_chunk.chunks,
799 &[
800 ChunkRow {
801 linked_chunk_id: linked_chunk_id.clone(),
802 previous_chunk: Some(CId::new(3)),
803 chunk: CId::new(0),
804 next_chunk: Some(CId::new(1))
805 },
806 ChunkRow {
807 linked_chunk_id: linked_chunk_id.clone(),
808 previous_chunk: Some(CId::new(0)),
809 chunk: CId::new(1),
810 next_chunk: None
811 },
812 ChunkRow {
813 linked_chunk_id: linked_chunk_id.clone(),
814 previous_chunk: None,
815 chunk: CId::new(2),
816 next_chunk: Some(CId::new(3))
817 },
818 ChunkRow {
819 linked_chunk_id,
820 previous_chunk: Some(CId::new(2)),
821 chunk: CId::new(3),
822 next_chunk: Some(CId::new(0))
823 },
824 ],
825 );
826
827 assert!(relational_linked_chunk.items_chunks.is_empty());
829 }
830
831 #[test]
832 fn test_new_gap_chunk() {
833 let room_id = room_id!("!r0:matrix.org");
834 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
835
836 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
837
838 relational_linked_chunk
839 .apply_updates(
840 linked_chunk_id.as_ref(),
841 vec![
842 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
844 Update::NewGapChunk {
846 previous: Some(CId::new(0)),
847 new: CId::new(1),
848 next: None,
849 gap: (),
850 },
851 Update::NewItemsChunk {
853 previous: Some(CId::new(1)),
854 new: CId::new(2),
855 next: None,
856 },
857 ],
858 )
859 .unwrap();
860
861 assert_eq!(
863 relational_linked_chunk.chunks,
864 &[
865 ChunkRow {
866 linked_chunk_id: linked_chunk_id.clone(),
867 previous_chunk: None,
868 chunk: CId::new(0),
869 next_chunk: Some(CId::new(1))
870 },
871 ChunkRow {
872 linked_chunk_id: linked_chunk_id.clone(),
873 previous_chunk: Some(CId::new(0)),
874 chunk: CId::new(1),
875 next_chunk: Some(CId::new(2))
876 },
877 ChunkRow {
878 linked_chunk_id: linked_chunk_id.clone(),
879 previous_chunk: Some(CId::new(1)),
880 chunk: CId::new(2),
881 next_chunk: None
882 },
883 ],
884 );
885 assert_eq!(
887 relational_linked_chunk.items_chunks,
888 &[ItemRow {
889 linked_chunk_id,
890 position: Position::new(CId::new(1), 0),
891 item: Either::Gap(())
892 }],
893 );
894 }
895
896 #[test]
897 fn test_remove_chunk() {
898 let room_id = room_id!("!r0:matrix.org");
899 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
900
901 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
902
903 relational_linked_chunk
904 .apply_updates(
905 linked_chunk_id.as_ref(),
906 vec![
907 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
909 Update::NewGapChunk {
911 previous: Some(CId::new(0)),
912 new: CId::new(1),
913 next: None,
914 gap: (),
915 },
916 Update::NewItemsChunk {
918 previous: Some(CId::new(1)),
919 new: CId::new(2),
920 next: None,
921 },
922 Update::RemoveChunk(CId::new(1)),
924 ],
925 )
926 .unwrap();
927
928 assert_eq!(
930 relational_linked_chunk.chunks,
931 &[
932 ChunkRow {
933 linked_chunk_id: linked_chunk_id.clone(),
934 previous_chunk: None,
935 chunk: CId::new(0),
936 next_chunk: Some(CId::new(2))
937 },
938 ChunkRow {
939 linked_chunk_id,
940 previous_chunk: Some(CId::new(0)),
941 chunk: CId::new(2),
942 next_chunk: None
943 },
944 ],
945 );
946
947 assert!(relational_linked_chunk.items_chunks.is_empty());
949 }
950
951 #[test]
952 fn test_push_items() {
953 let room_id = room_id!("!r0:matrix.org");
954 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
955
956 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
957
958 relational_linked_chunk
959 .apply_updates(
960 linked_chunk_id.as_ref(),
961 vec![
962 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
965 Update::PushItems {
967 at: Position::new(CId::new(0), 0),
968 items: vec!['a', 'b', 'c'],
969 },
970 Update::NewItemsChunk {
972 previous: Some(CId::new(0)),
973 new: CId::new(1),
974 next: None,
975 },
976 Update::PushItems {
978 at: Position::new(CId::new(1), 0),
979 items: vec!['x', 'y', 'z'],
980 },
981 Update::PushItems { at: Position::new(CId::new(0), 3), items: vec!['d', 'e'] },
983 ],
984 )
985 .unwrap();
986
987 assert_eq!(
989 relational_linked_chunk.chunks,
990 &[
991 ChunkRow {
992 linked_chunk_id: linked_chunk_id.clone(),
993 previous_chunk: None,
994 chunk: CId::new(0),
995 next_chunk: Some(CId::new(1))
996 },
997 ChunkRow {
998 linked_chunk_id: linked_chunk_id.clone(),
999 previous_chunk: Some(CId::new(0)),
1000 chunk: CId::new(1),
1001 next_chunk: None
1002 },
1003 ],
1004 );
1005 assert_eq!(
1007 relational_linked_chunk.items_chunks,
1008 &[
1009 ItemRow {
1010 linked_chunk_id: linked_chunk_id.clone(),
1011 position: Position::new(CId::new(0), 0),
1012 item: Either::Item('a')
1013 },
1014 ItemRow {
1015 linked_chunk_id: linked_chunk_id.clone(),
1016 position: Position::new(CId::new(0), 1),
1017 item: Either::Item('b')
1018 },
1019 ItemRow {
1020 linked_chunk_id: linked_chunk_id.clone(),
1021 position: Position::new(CId::new(0), 2),
1022 item: Either::Item('c')
1023 },
1024 ItemRow {
1025 linked_chunk_id: linked_chunk_id.clone(),
1026 position: Position::new(CId::new(1), 0),
1027 item: Either::Item('x')
1028 },
1029 ItemRow {
1030 linked_chunk_id: linked_chunk_id.clone(),
1031 position: Position::new(CId::new(1), 1),
1032 item: Either::Item('y')
1033 },
1034 ItemRow {
1035 linked_chunk_id: linked_chunk_id.clone(),
1036 position: Position::new(CId::new(1), 2),
1037 item: Either::Item('z')
1038 },
1039 ItemRow {
1040 linked_chunk_id: linked_chunk_id.clone(),
1041 position: Position::new(CId::new(0), 3),
1042 item: Either::Item('d')
1043 },
1044 ItemRow {
1045 linked_chunk_id: linked_chunk_id.clone(),
1046 position: Position::new(CId::new(0), 4),
1047 item: Either::Item('e')
1048 },
1049 ],
1050 );
1051 }
1052
1053 #[test]
1054 fn test_remove_item() {
1055 let room_id = room_id!("!r0:matrix.org");
1056 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
1057
1058 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
1059
1060 relational_linked_chunk
1061 .apply_updates(
1062 linked_chunk_id.as_ref(),
1063 vec![
1064 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1067 Update::PushItems {
1069 at: Position::new(CId::new(0), 0),
1070 items: vec!['a', 'b', 'c', 'd', 'e'],
1071 },
1072 Update::RemoveItem { at: Position::new(CId::new(0), 0) },
1074 Update::RemoveItem { at: Position::new(CId::new(0), 2) },
1076 ],
1077 )
1078 .unwrap();
1079
1080 assert_eq!(
1082 relational_linked_chunk.chunks,
1083 &[ChunkRow {
1084 linked_chunk_id: linked_chunk_id.clone(),
1085 previous_chunk: None,
1086 chunk: CId::new(0),
1087 next_chunk: None
1088 }],
1089 );
1090 assert_eq!(
1092 relational_linked_chunk.items_chunks,
1093 &[
1094 ItemRow {
1095 linked_chunk_id: linked_chunk_id.clone(),
1096 position: Position::new(CId::new(0), 0),
1097 item: Either::Item('b')
1098 },
1099 ItemRow {
1100 linked_chunk_id: linked_chunk_id.clone(),
1101 position: Position::new(CId::new(0), 1),
1102 item: Either::Item('c')
1103 },
1104 ItemRow {
1105 linked_chunk_id: linked_chunk_id.clone(),
1106 position: Position::new(CId::new(0), 2),
1107 item: Either::Item('e')
1108 },
1109 ],
1110 );
1111 }
1112
1113 #[test]
1114 fn test_detach_last_items() {
1115 let room_id = room_id!("!r0:matrix.org");
1116 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
1117
1118 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
1119
1120 relational_linked_chunk
1121 .apply_updates(
1122 linked_chunk_id.as_ref(),
1123 vec![
1124 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1126 Update::NewItemsChunk {
1128 previous: Some(CId::new(0)),
1129 new: CId::new(1),
1130 next: None,
1131 },
1132 Update::PushItems {
1134 at: Position::new(CId::new(0), 0),
1135 items: vec!['a', 'b', 'c', 'd', 'e'],
1136 },
1137 Update::PushItems {
1139 at: Position::new(CId::new(1), 0),
1140 items: vec!['x', 'y', 'z'],
1141 },
1142 Update::DetachLastItems { at: Position::new(CId::new(0), 2) },
1144 ],
1145 )
1146 .unwrap();
1147
1148 assert_eq!(
1150 relational_linked_chunk.chunks,
1151 &[
1152 ChunkRow {
1153 linked_chunk_id: linked_chunk_id.clone(),
1154 previous_chunk: None,
1155 chunk: CId::new(0),
1156 next_chunk: Some(CId::new(1))
1157 },
1158 ChunkRow {
1159 linked_chunk_id: linked_chunk_id.clone(),
1160 previous_chunk: Some(CId::new(0)),
1161 chunk: CId::new(1),
1162 next_chunk: None
1163 },
1164 ],
1165 );
1166 assert_eq!(
1168 relational_linked_chunk.items_chunks,
1169 &[
1170 ItemRow {
1171 linked_chunk_id: linked_chunk_id.clone(),
1172 position: Position::new(CId::new(0), 0),
1173 item: Either::Item('a')
1174 },
1175 ItemRow {
1176 linked_chunk_id: linked_chunk_id.clone(),
1177 position: Position::new(CId::new(0), 1),
1178 item: Either::Item('b')
1179 },
1180 ItemRow {
1181 linked_chunk_id: linked_chunk_id.clone(),
1182 position: Position::new(CId::new(1), 0),
1183 item: Either::Item('x')
1184 },
1185 ItemRow {
1186 linked_chunk_id: linked_chunk_id.clone(),
1187 position: Position::new(CId::new(1), 1),
1188 item: Either::Item('y')
1189 },
1190 ItemRow {
1191 linked_chunk_id: linked_chunk_id.clone(),
1192 position: Position::new(CId::new(1), 2),
1193 item: Either::Item('z')
1194 },
1195 ],
1196 );
1197 }
1198
1199 #[test]
1200 fn test_start_and_end_reattach_items() {
1201 let room_id = room_id!("!r0:matrix.org");
1202 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
1203
1204 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
1205
1206 relational_linked_chunk
1207 .apply_updates(
1208 linked_chunk_id.as_ref(),
1209 vec![Update::StartReattachItems, Update::EndReattachItems],
1210 )
1211 .unwrap();
1212
1213 assert!(relational_linked_chunk.chunks.is_empty());
1215 assert!(relational_linked_chunk.items_chunks.is_empty());
1216 }
1217
1218 #[test]
1219 fn test_clear() {
1220 let r0 = room_id!("!r0:matrix.org");
1221 let linked_chunk_id0 = OwnedLinkedChunkId::Room(r0.to_owned());
1222
1223 let r1 = room_id!("!r1:matrix.org");
1224 let linked_chunk_id1 = OwnedLinkedChunkId::Room(r1.to_owned());
1225
1226 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
1227
1228 relational_linked_chunk
1229 .apply_updates(
1230 linked_chunk_id0.as_ref(),
1231 vec![
1232 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1235 Update::PushItems {
1237 at: Position::new(CId::new(0), 0),
1238 items: vec!['a', 'b', 'c'],
1239 },
1240 ],
1241 )
1242 .unwrap();
1243
1244 relational_linked_chunk
1245 .apply_updates(
1246 linked_chunk_id1.as_ref(),
1247 vec![
1248 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1251 Update::PushItems { at: Position::new(CId::new(0), 0), items: vec!['x'] },
1253 ],
1254 )
1255 .unwrap();
1256
1257 assert_eq!(
1259 relational_linked_chunk.chunks,
1260 &[
1261 ChunkRow {
1262 linked_chunk_id: linked_chunk_id0.to_owned(),
1263 previous_chunk: None,
1264 chunk: CId::new(0),
1265 next_chunk: None,
1266 },
1267 ChunkRow {
1268 linked_chunk_id: linked_chunk_id1.to_owned(),
1269 previous_chunk: None,
1270 chunk: CId::new(0),
1271 next_chunk: None,
1272 }
1273 ],
1274 );
1275
1276 assert_eq!(
1278 relational_linked_chunk.items_chunks,
1279 &[
1280 ItemRow {
1281 linked_chunk_id: linked_chunk_id0.to_owned(),
1282 position: Position::new(CId::new(0), 0),
1283 item: Either::Item('a')
1284 },
1285 ItemRow {
1286 linked_chunk_id: linked_chunk_id0.to_owned(),
1287 position: Position::new(CId::new(0), 1),
1288 item: Either::Item('b')
1289 },
1290 ItemRow {
1291 linked_chunk_id: linked_chunk_id0.to_owned(),
1292 position: Position::new(CId::new(0), 2),
1293 item: Either::Item('c')
1294 },
1295 ItemRow {
1296 linked_chunk_id: linked_chunk_id1.to_owned(),
1297 position: Position::new(CId::new(0), 0),
1298 item: Either::Item('x')
1299 },
1300 ],
1301 );
1302
1303 relational_linked_chunk
1305 .apply_updates(linked_chunk_id0.as_ref(), vec![Update::Clear])
1306 .unwrap();
1307
1308 assert_eq!(
1310 relational_linked_chunk.chunks,
1311 &[ChunkRow {
1312 linked_chunk_id: linked_chunk_id1.to_owned(),
1313 previous_chunk: None,
1314 chunk: CId::new(0),
1315 next_chunk: None,
1316 }],
1317 );
1318
1319 assert_eq!(
1320 relational_linked_chunk.items_chunks,
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 #[test]
1330 fn test_load_empty_linked_chunk() {
1331 let room_id = room_id!("!r0:matrix.org");
1332 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
1333
1334 let relational_linked_chunk = RelationalLinkedChunk::<_, char, char>::new();
1336 let result = relational_linked_chunk.load_all_chunks(linked_chunk_id.as_ref()).unwrap();
1337 assert!(result.is_empty());
1338 }
1339
1340 #[test]
1341 fn test_load_all_chunks_with_empty_items() {
1342 let room_id = room_id!("!r0:matrix.org");
1343 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
1344
1345 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, char>::new();
1346
1347 relational_linked_chunk
1349 .apply_updates(
1350 linked_chunk_id.as_ref(),
1351 vec![Update::NewItemsChunk { previous: None, new: CId::new(0), next: None }],
1352 )
1353 .unwrap();
1354
1355 let lc = from_all_chunks::<3, _, _>(
1357 relational_linked_chunk.load_all_chunks(linked_chunk_id.as_ref()).unwrap(),
1358 )
1359 .expect("building succeeds")
1360 .expect("this leads to a non-empty linked chunk");
1361
1362 assert_items_eq!(lc, []);
1363 }
1364
1365 #[test]
1366 fn test_rebuild_linked_chunk() {
1367 let room_id = room_id!("!r0:matrix.org");
1368 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
1369
1370 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, char>::new();
1371
1372 relational_linked_chunk
1373 .apply_updates(
1374 linked_chunk_id.as_ref(),
1375 vec![
1376 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1378 Update::PushItems {
1380 at: Position::new(CId::new(0), 0),
1381 items: vec!['a', 'b', 'c'],
1382 },
1383 Update::NewGapChunk {
1385 previous: Some(CId::new(0)),
1386 new: CId::new(1),
1387 next: None,
1388 gap: 'g',
1389 },
1390 Update::NewItemsChunk {
1392 previous: Some(CId::new(1)),
1393 new: CId::new(2),
1394 next: None,
1395 },
1396 Update::PushItems {
1398 at: Position::new(CId::new(2), 0),
1399 items: vec!['d', 'e', 'f'],
1400 },
1401 ],
1402 )
1403 .unwrap();
1404
1405 let lc = from_all_chunks::<3, _, _>(
1406 relational_linked_chunk.load_all_chunks(linked_chunk_id.as_ref()).unwrap(),
1407 )
1408 .expect("building succeeds")
1409 .expect("this leads to a non-empty linked chunk");
1410
1411 assert_items_eq!(lc, ['a', 'b', 'c'] [-] ['d', 'e', 'f']);
1413 }
1414
1415 #[test]
1416 fn test_replace_item() {
1417 let room_id = room_id!("!r0:matrix.org");
1418 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
1419
1420 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
1421
1422 relational_linked_chunk
1423 .apply_updates(
1424 linked_chunk_id.as_ref(),
1425 vec![
1426 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1429 Update::PushItems {
1431 at: Position::new(CId::new(0), 0),
1432 items: vec!['a', 'b', 'c'],
1433 },
1434 Update::ReplaceItem { at: Position::new(CId::new(0), 1), item: 'B' },
1436 ],
1437 )
1438 .unwrap();
1439
1440 assert_eq!(
1442 relational_linked_chunk.chunks,
1443 &[ChunkRow {
1444 linked_chunk_id: linked_chunk_id.clone(),
1445 previous_chunk: None,
1446 chunk: CId::new(0),
1447 next_chunk: None,
1448 },],
1449 );
1450
1451 assert_eq!(
1453 relational_linked_chunk.items_chunks,
1454 &[
1455 ItemRow {
1456 linked_chunk_id: linked_chunk_id.clone(),
1457 position: Position::new(CId::new(0), 0),
1458 item: Either::Item('a')
1459 },
1460 ItemRow {
1461 linked_chunk_id: linked_chunk_id.clone(),
1462 position: Position::new(CId::new(0), 1),
1463 item: Either::Item('B')
1464 },
1465 ItemRow {
1466 linked_chunk_id,
1467 position: Position::new(CId::new(0), 2),
1468 item: Either::Item('c')
1469 },
1470 ],
1471 );
1472 }
1473
1474 #[test]
1475 fn test_unordered_events() {
1476 let room_id = room_id!("!r0:matrix.org");
1477 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
1478
1479 let other_room_id = room_id!("!r1:matrix.org");
1480 let other_linked_chunk_id = OwnedLinkedChunkId::Room(other_room_id.to_owned());
1481
1482 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
1483
1484 relational_linked_chunk
1485 .apply_updates(
1486 linked_chunk_id.as_ref(),
1487 vec![
1488 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1489 Update::PushItems {
1490 at: Position::new(CId::new(0), 0),
1491 items: vec!['a', 'b', 'c'],
1492 },
1493 Update::NewItemsChunk {
1494 previous: Some(CId::new(0)),
1495 new: CId::new(1),
1496 next: None,
1497 },
1498 Update::PushItems {
1499 at: Position::new(CId::new(1), 0),
1500 items: vec!['d', 'e', 'f'],
1501 },
1502 ],
1503 )
1504 .unwrap();
1505
1506 relational_linked_chunk
1507 .apply_updates(
1508 other_linked_chunk_id.as_ref(),
1509 vec![
1510 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1511 Update::PushItems {
1512 at: Position::new(CId::new(0), 0),
1513 items: vec!['x', 'y', 'z'],
1514 },
1515 ],
1516 )
1517 .unwrap();
1518
1519 let events = BTreeMap::from_iter(
1520 relational_linked_chunk.unordered_linked_chunk_items(&linked_chunk_id),
1521 );
1522
1523 assert_eq!(events.len(), 6);
1524 assert_eq!(*events.get(&'a').unwrap(), Position::new(CId::new(0), 0));
1525 assert_eq!(*events.get(&'b').unwrap(), Position::new(CId::new(0), 1));
1526 assert_eq!(*events.get(&'c').unwrap(), Position::new(CId::new(0), 2));
1527 assert_eq!(*events.get(&'d').unwrap(), Position::new(CId::new(1), 0));
1528 assert_eq!(*events.get(&'e').unwrap(), Position::new(CId::new(1), 1));
1529 assert_eq!(*events.get(&'f').unwrap(), Position::new(CId::new(1), 2));
1530 }
1531
1532 #[test]
1533 fn test_load_last_chunk() {
1534 let room_id = room_id!("!r0:matrix.org");
1535 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
1536
1537 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
1538
1539 {
1541 let (last_chunk, chunk_identifier_generator) =
1542 relational_linked_chunk.load_last_chunk(linked_chunk_id.as_ref()).unwrap();
1543
1544 assert!(last_chunk.is_none());
1545 assert_eq!(chunk_identifier_generator.current(), 0);
1546 }
1547
1548 {
1550 relational_linked_chunk
1551 .apply_updates(
1552 linked_chunk_id.as_ref(),
1553 vec![
1554 Update::NewItemsChunk { previous: None, new: CId::new(42), next: None },
1555 Update::PushItems {
1556 at: Position::new(CId::new(42), 0),
1557 items: vec!['a', 'b'],
1558 },
1559 ],
1560 )
1561 .unwrap();
1562
1563 let (last_chunk, chunk_identifier_generator) =
1564 relational_linked_chunk.load_last_chunk(linked_chunk_id.as_ref()).unwrap();
1565
1566 assert_matches!(last_chunk, Some(last_chunk) => {
1567 assert_eq!(last_chunk.identifier, 42);
1568 assert!(last_chunk.previous.is_none());
1569 assert!(last_chunk.next.is_none());
1570 assert_matches!(last_chunk.content, ChunkContent::Items(items) => {
1571 assert_eq!(items.len(), 2);
1572 assert_eq!(items, &['a', 'b']);
1573 });
1574 });
1575 assert_eq!(chunk_identifier_generator.current(), 42);
1576 }
1577
1578 {
1580 relational_linked_chunk
1581 .apply_updates(
1582 linked_chunk_id.as_ref(),
1583 vec![
1584 Update::NewItemsChunk {
1585 previous: Some(CId::new(42)),
1586 new: CId::new(7),
1587 next: None,
1588 },
1589 Update::PushItems {
1590 at: Position::new(CId::new(7), 0),
1591 items: vec!['c', 'd', 'e'],
1592 },
1593 ],
1594 )
1595 .unwrap();
1596
1597 let (last_chunk, chunk_identifier_generator) =
1598 relational_linked_chunk.load_last_chunk(linked_chunk_id.as_ref()).unwrap();
1599
1600 assert_matches!(last_chunk, Some(last_chunk) => {
1601 assert_eq!(last_chunk.identifier, 7);
1602 assert_matches!(last_chunk.previous, Some(previous) => {
1603 assert_eq!(previous, 42);
1604 });
1605 assert!(last_chunk.next.is_none());
1606 assert_matches!(last_chunk.content, ChunkContent::Items(items) => {
1607 assert_eq!(items.len(), 3);
1608 assert_eq!(items, &['c', 'd', 'e']);
1609 });
1610 });
1611 assert_eq!(chunk_identifier_generator.current(), 42);
1612 }
1613 }
1614
1615 #[test]
1616 fn test_load_last_chunk_with_a_cycle() {
1617 let room_id = room_id!("!r0:matrix.org");
1618 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
1619 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
1620
1621 relational_linked_chunk
1622 .apply_updates(
1623 linked_chunk_id.as_ref(),
1624 vec![
1625 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1626 Update::NewItemsChunk {
1627 previous: Some(CId::new(0)),
1631 new: CId::new(1),
1632 next: Some(CId::new(0)),
1633 },
1634 ],
1635 )
1636 .unwrap();
1637
1638 relational_linked_chunk.load_last_chunk(linked_chunk_id.as_ref()).unwrap_err();
1639 }
1640
1641 #[test]
1642 fn test_load_previous_chunk() {
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 {
1650 let previous_chunk = relational_linked_chunk
1651 .load_previous_chunk(linked_chunk_id.as_ref(), CId::new(153))
1652 .unwrap();
1653
1654 assert!(previous_chunk.is_none());
1655 }
1656
1657 {
1660 relational_linked_chunk
1661 .apply_updates(
1662 linked_chunk_id.as_ref(),
1663 vec![Update::NewItemsChunk { previous: None, new: CId::new(42), next: None }],
1664 )
1665 .unwrap();
1666
1667 let previous_chunk = relational_linked_chunk
1668 .load_previous_chunk(linked_chunk_id.as_ref(), CId::new(42))
1669 .unwrap();
1670
1671 assert!(previous_chunk.is_none());
1672 }
1673
1674 {
1676 relational_linked_chunk
1677 .apply_updates(
1678 linked_chunk_id.as_ref(),
1679 vec![
1680 Update::NewItemsChunk {
1682 previous: None,
1683 new: CId::new(7),
1684 next: Some(CId::new(42)),
1685 },
1686 Update::PushItems {
1687 at: Position::new(CId::new(7), 0),
1688 items: vec!['a', 'b', 'c'],
1689 },
1690 ],
1691 )
1692 .unwrap();
1693
1694 let previous_chunk = relational_linked_chunk
1695 .load_previous_chunk(linked_chunk_id.as_ref(), CId::new(42))
1696 .unwrap();
1697
1698 assert_matches!(previous_chunk, Some(previous_chunk) => {
1699 assert_eq!(previous_chunk.identifier, 7);
1700 assert!(previous_chunk.previous.is_none());
1701 assert_matches!(previous_chunk.next, Some(next) => {
1702 assert_eq!(next, 42);
1703 });
1704 assert_matches!(previous_chunk.content, ChunkContent::Items(items) => {
1705 assert_eq!(items.len(), 3);
1706 assert_eq!(items, &['a', 'b', 'c']);
1707 });
1708 });
1709 }
1710 }
1711}