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