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 }
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>(
406 &'a self,
407 room_id: &'a RoomId,
408 ) -> impl Iterator<Item = (&'a OwnedLinkedChunkId, (&'a Item, Option<Position>))> {
409 self.items
410 .iter()
411 .filter(move |(linked_chunk_id, _)| linked_chunk_id.room_id() == room_id)
412 .flat_map(|(linked_chunk_id, items)| {
413 items.values().map(move |(item, pos)| (linked_chunk_id, (item, *pos)))
414 })
415 }
416}
417
418impl<ItemId, Item, Gap> RelationalLinkedChunk<ItemId, Item, Gap>
419where
420 Item: IndexableItem<ItemId = ItemId> + Clone,
421 ItemId: Hash + PartialEq + Eq + Clone + Ord,
422{
423 pub fn save_item(&mut self, room_id: OwnedRoomId, item: Item) {
425 let id = item.id();
426
427 let mut linked_chunk_ids = self
428 .items
429 .keys()
430 .filter(|linked_chunk_id| linked_chunk_id.room_id() == room_id)
431 .cloned()
432 .collect::<HashSet<_>>();
433 linked_chunk_ids.insert(OwnedLinkedChunkId::Room(room_id));
434
435 for linked_chunk_id in linked_chunk_ids {
436 let map = self.items.entry(linked_chunk_id).or_default();
437 if let Some(prev_value) = map.get_mut(&id) {
438 prev_value.0 = item.clone();
440 } else {
441 map.insert(id.clone(), (item.clone(), None));
442 }
443 }
444 }
445}
446
447impl<ItemId, Item, Gap> RelationalLinkedChunk<ItemId, Item, Gap>
448where
449 Gap: Clone,
450 Item: Clone,
451 ItemId: Hash + PartialEq + Eq + Ord,
452{
453 #[doc(hidden)]
458 pub fn load_all_chunks(
459 &self,
460 linked_chunk_id: LinkedChunkId<'_>,
461 ) -> Result<Vec<RawChunk<Item, Gap>>, String> {
462 self.chunks
463 .iter()
464 .filter(|chunk| chunk.linked_chunk_id == linked_chunk_id)
465 .map(|chunk_row| load_raw_chunk(self, chunk_row, linked_chunk_id))
466 .collect::<Result<Vec<_>, String>>()
467 }
468
469 #[doc(hidden)]
474 pub fn load_all_chunks_metadata(
475 &self,
476 linked_chunk_id: LinkedChunkId<'_>,
477 ) -> Result<Vec<ChunkMetadata>, String> {
478 self.chunks
479 .iter()
480 .filter(|chunk| chunk.linked_chunk_id == linked_chunk_id)
481 .map(|chunk_row| load_raw_chunk_metadata(self, chunk_row, linked_chunk_id))
482 .collect::<Result<Vec<_>, String>>()
483 }
484
485 pub fn load_last_chunk(
486 &self,
487 linked_chunk_id: LinkedChunkId<'_>,
488 ) -> Result<(Option<RawChunk<Item, Gap>>, ChunkIdentifierGenerator), String> {
489 let chunk_identifier_generator = match self
491 .chunks
492 .iter()
493 .filter_map(|chunk_row| {
494 (chunk_row.linked_chunk_id == linked_chunk_id).then_some(chunk_row.chunk)
495 })
496 .max()
497 {
498 Some(last_chunk_identifier) => {
499 ChunkIdentifierGenerator::new_from_previous_chunk_identifier(last_chunk_identifier)
500 }
501 None => ChunkIdentifierGenerator::new_from_scratch(),
502 };
503
504 let mut number_of_chunks = 0;
506 let mut chunk_row = None;
507
508 for chunk_row_candidate in &self.chunks {
509 if chunk_row_candidate.linked_chunk_id == linked_chunk_id {
510 number_of_chunks += 1;
511
512 if chunk_row_candidate.next_chunk.is_none() {
513 chunk_row = Some(chunk_row_candidate);
514
515 break;
516 }
517 }
518 }
519
520 let chunk_row = match chunk_row {
521 Some(chunk_row) => chunk_row,
523
524 None if number_of_chunks == 0 => {
527 return Ok((None, chunk_identifier_generator));
528 }
529
530 None => {
535 return Err(
536 "last chunk is not found but chunks exist: the linked chunk contains a cycle"
537 .to_owned(),
538 );
539 }
540 };
541
542 load_raw_chunk(self, chunk_row, linked_chunk_id)
544 .map(|raw_chunk| (Some(raw_chunk), chunk_identifier_generator))
545 }
546
547 pub fn load_previous_chunk(
548 &self,
549 linked_chunk_id: LinkedChunkId<'_>,
550 before_chunk_identifier: ChunkIdentifier,
551 ) -> Result<Option<RawChunk<Item, Gap>>, String> {
552 let Some(chunk_row) = self.chunks.iter().find(|chunk_row| {
554 chunk_row.linked_chunk_id == linked_chunk_id
555 && chunk_row.next_chunk == Some(before_chunk_identifier)
556 }) else {
557 return Ok(None);
559 };
560
561 load_raw_chunk(self, chunk_row, linked_chunk_id).map(Some)
563 }
564}
565
566impl<ItemId, Item, Gap> Default for RelationalLinkedChunk<ItemId, Item, Gap>
567where
568 Item: IndexableItem<ItemId = ItemId>,
569 ItemId: Hash + PartialEq + Eq + Clone + Ord,
570{
571 fn default() -> Self {
572 Self::new()
573 }
574}
575
576fn load_raw_chunk<ItemId, Item, Gap>(
581 relational_linked_chunk: &RelationalLinkedChunk<ItemId, Item, Gap>,
582 chunk_row: &ChunkRow,
583 linked_chunk_id: LinkedChunkId<'_>,
584) -> Result<RawChunk<Item, Gap>, String>
585where
586 Item: Clone,
587 Gap: Clone,
588 ItemId: Hash + PartialEq + Eq + Ord,
589{
590 let mut items = relational_linked_chunk
592 .items_chunks
593 .iter()
594 .filter(|item_row| {
595 item_row.linked_chunk_id == linked_chunk_id
596 && item_row.position.chunk_identifier() == chunk_row.chunk
597 })
598 .peekable();
599
600 let Some(first_item) = items.peek() else {
601 return Ok(RawChunk {
603 content: ChunkContent::Items(Vec::new()),
604 previous: chunk_row.previous_chunk,
605 identifier: chunk_row.chunk,
606 next: chunk_row.next_chunk,
607 });
608 };
609
610 Ok(match first_item.item {
611 Either::Item(_) => {
613 let mut collected_items = Vec::new();
615
616 for item_row in items {
617 match &item_row.item {
618 Either::Item(item_id) => {
619 collected_items.push((item_id, item_row.position.index()))
620 }
621
622 Either::Gap(_) => {
623 return Err(format!(
624 "unexpected gap in items chunk {}",
625 chunk_row.chunk.index()
626 ));
627 }
628 }
629 }
630
631 collected_items.sort_unstable_by_key(|(_item, index)| *index);
633
634 RawChunk {
635 content: ChunkContent::Items(
636 collected_items
637 .into_iter()
638 .filter_map(|(item_id, _index)| {
639 Some(
640 relational_linked_chunk
641 .items
642 .get(&linked_chunk_id.to_owned())?
643 .get(item_id)?
644 .0
645 .clone(),
646 )
647 })
648 .collect(),
649 ),
650 previous: chunk_row.previous_chunk,
651 identifier: chunk_row.chunk,
652 next: chunk_row.next_chunk,
653 }
654 }
655
656 Either::Gap(ref gap) => {
657 assert!(items.next().is_some(), "we just peeked the gap");
658
659 if items.next().is_some() {
661 return Err(format!(
662 "there shouldn't be more than one item row attached in gap chunk {}",
663 chunk_row.chunk.index()
664 ));
665 }
666
667 RawChunk {
668 content: ChunkContent::Gap(gap.clone()),
669 previous: chunk_row.previous_chunk,
670 identifier: chunk_row.chunk,
671 next: chunk_row.next_chunk,
672 }
673 }
674 })
675}
676
677fn load_raw_chunk_metadata<ItemId, Item, Gap>(
682 relational_linked_chunk: &RelationalLinkedChunk<ItemId, Item, Gap>,
683 chunk_row: &ChunkRow,
684 linked_chunk_id: LinkedChunkId<'_>,
685) -> Result<ChunkMetadata, String>
686where
687 Item: Clone,
688 Gap: Clone,
689 ItemId: Hash + PartialEq + Eq,
690{
691 let mut items = relational_linked_chunk
693 .items_chunks
694 .iter()
695 .filter(|item_row| {
696 item_row.linked_chunk_id == linked_chunk_id
697 && item_row.position.chunk_identifier() == chunk_row.chunk
698 })
699 .peekable();
700
701 let Some(first_item) = items.peek() else {
702 return Ok(ChunkMetadata {
704 num_items: 0,
705 previous: chunk_row.previous_chunk,
706 identifier: chunk_row.chunk,
707 next: chunk_row.next_chunk,
708 });
709 };
710
711 Ok(match first_item.item {
712 Either::Item(_) => {
714 let mut num_items = 0;
718 for item in items {
719 match &item.item {
720 Either::Item(_) => num_items += 1,
721 Either::Gap(_) => {
722 return Err(format!(
723 "unexpected gap in items chunk {}",
724 chunk_row.chunk.index()
725 ));
726 }
727 }
728 }
729
730 ChunkMetadata {
731 num_items,
732 previous: chunk_row.previous_chunk,
733 identifier: chunk_row.chunk,
734 next: chunk_row.next_chunk,
735 }
736 }
737
738 Either::Gap(..) => {
739 assert!(items.next().is_some(), "we just peeked the gap");
740
741 if items.next().is_some() {
743 return Err(format!(
744 "there shouldn't be more than one item row attached in gap chunk {}",
745 chunk_row.chunk.index()
746 ));
747 }
748
749 ChunkMetadata {
750 num_items: 0,
752 previous: chunk_row.previous_chunk,
753 identifier: chunk_row.chunk,
754 next: chunk_row.next_chunk,
755 }
756 }
757 })
758}
759
760#[cfg(test)]
761mod tests {
762 use std::collections::BTreeMap;
763
764 use assert_matches::assert_matches;
765 use ruma::room_id;
766
767 use super::{super::lazy_loader::from_all_chunks, ChunkIdentifier as CId, *};
768
769 impl IndexableItem for char {
770 type ItemId = char;
771
772 fn id(&self) -> Self::ItemId {
773 *self
774 }
775 }
776
777 #[test]
778 fn test_new_items_chunk() {
779 let room_id = room_id!("!r0:matrix.org");
780 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
781
782 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
783
784 relational_linked_chunk
785 .apply_updates(
786 linked_chunk_id.as_ref(),
787 vec![
788 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
790 Update::NewItemsChunk {
792 previous: Some(CId::new(0)),
793 new: CId::new(1),
794 next: None,
795 },
796 Update::NewItemsChunk {
798 previous: None,
799 new: CId::new(2),
800 next: Some(CId::new(0)),
801 },
802 Update::NewItemsChunk {
804 previous: Some(CId::new(2)),
805 new: CId::new(3),
806 next: Some(CId::new(0)),
807 },
808 ],
809 )
810 .unwrap();
811
812 assert_eq!(
814 relational_linked_chunk.chunks,
815 &[
816 ChunkRow {
817 linked_chunk_id: linked_chunk_id.clone(),
818 previous_chunk: Some(CId::new(3)),
819 chunk: CId::new(0),
820 next_chunk: Some(CId::new(1))
821 },
822 ChunkRow {
823 linked_chunk_id: linked_chunk_id.clone(),
824 previous_chunk: Some(CId::new(0)),
825 chunk: CId::new(1),
826 next_chunk: None
827 },
828 ChunkRow {
829 linked_chunk_id: linked_chunk_id.clone(),
830 previous_chunk: None,
831 chunk: CId::new(2),
832 next_chunk: Some(CId::new(3))
833 },
834 ChunkRow {
835 linked_chunk_id,
836 previous_chunk: Some(CId::new(2)),
837 chunk: CId::new(3),
838 next_chunk: Some(CId::new(0))
839 },
840 ],
841 );
842
843 assert!(relational_linked_chunk.items_chunks.is_empty());
845 }
846
847 #[test]
848 fn test_new_gap_chunk() {
849 let room_id = room_id!("!r0:matrix.org");
850 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
851
852 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
853
854 relational_linked_chunk
855 .apply_updates(
856 linked_chunk_id.as_ref(),
857 vec![
858 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
860 Update::NewGapChunk {
862 previous: Some(CId::new(0)),
863 new: CId::new(1),
864 next: None,
865 gap: (),
866 },
867 Update::NewItemsChunk {
869 previous: Some(CId::new(1)),
870 new: CId::new(2),
871 next: None,
872 },
873 ],
874 )
875 .unwrap();
876
877 assert_eq!(
879 relational_linked_chunk.chunks,
880 &[
881 ChunkRow {
882 linked_chunk_id: linked_chunk_id.clone(),
883 previous_chunk: None,
884 chunk: CId::new(0),
885 next_chunk: Some(CId::new(1))
886 },
887 ChunkRow {
888 linked_chunk_id: linked_chunk_id.clone(),
889 previous_chunk: Some(CId::new(0)),
890 chunk: CId::new(1),
891 next_chunk: Some(CId::new(2))
892 },
893 ChunkRow {
894 linked_chunk_id: linked_chunk_id.clone(),
895 previous_chunk: Some(CId::new(1)),
896 chunk: CId::new(2),
897 next_chunk: None
898 },
899 ],
900 );
901 assert_eq!(
903 relational_linked_chunk.items_chunks,
904 &[ItemRow {
905 linked_chunk_id,
906 position: Position::new(CId::new(1), 0),
907 item: Either::Gap(())
908 }],
909 );
910 }
911
912 #[test]
913 fn test_remove_chunk() {
914 let room_id = room_id!("!r0:matrix.org");
915 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
916
917 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
918
919 relational_linked_chunk
920 .apply_updates(
921 linked_chunk_id.as_ref(),
922 vec![
923 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
925 Update::NewGapChunk {
927 previous: Some(CId::new(0)),
928 new: CId::new(1),
929 next: None,
930 gap: (),
931 },
932 Update::NewItemsChunk {
934 previous: Some(CId::new(1)),
935 new: CId::new(2),
936 next: None,
937 },
938 Update::RemoveChunk(CId::new(1)),
940 ],
941 )
942 .unwrap();
943
944 assert_eq!(
946 relational_linked_chunk.chunks,
947 &[
948 ChunkRow {
949 linked_chunk_id: linked_chunk_id.clone(),
950 previous_chunk: None,
951 chunk: CId::new(0),
952 next_chunk: Some(CId::new(2))
953 },
954 ChunkRow {
955 linked_chunk_id,
956 previous_chunk: Some(CId::new(0)),
957 chunk: CId::new(2),
958 next_chunk: None
959 },
960 ],
961 );
962
963 assert!(relational_linked_chunk.items_chunks.is_empty());
965 }
966
967 #[test]
968 fn test_push_items() {
969 let room_id = room_id!("!r0:matrix.org");
970 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
971
972 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
973
974 relational_linked_chunk
975 .apply_updates(
976 linked_chunk_id.as_ref(),
977 vec![
978 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
981 Update::PushItems {
983 at: Position::new(CId::new(0), 0),
984 items: vec!['a', 'b', 'c'],
985 },
986 Update::NewItemsChunk {
988 previous: Some(CId::new(0)),
989 new: CId::new(1),
990 next: None,
991 },
992 Update::PushItems {
994 at: Position::new(CId::new(1), 0),
995 items: vec!['x', 'y', 'z'],
996 },
997 Update::PushItems { at: Position::new(CId::new(0), 3), items: vec!['d', 'e'] },
999 ],
1000 )
1001 .unwrap();
1002
1003 assert_eq!(
1005 relational_linked_chunk.chunks,
1006 &[
1007 ChunkRow {
1008 linked_chunk_id: linked_chunk_id.clone(),
1009 previous_chunk: None,
1010 chunk: CId::new(0),
1011 next_chunk: Some(CId::new(1))
1012 },
1013 ChunkRow {
1014 linked_chunk_id: linked_chunk_id.clone(),
1015 previous_chunk: Some(CId::new(0)),
1016 chunk: CId::new(1),
1017 next_chunk: None
1018 },
1019 ],
1020 );
1021 assert_eq!(
1023 relational_linked_chunk.items_chunks,
1024 &[
1025 ItemRow {
1026 linked_chunk_id: linked_chunk_id.clone(),
1027 position: Position::new(CId::new(0), 0),
1028 item: Either::Item('a')
1029 },
1030 ItemRow {
1031 linked_chunk_id: linked_chunk_id.clone(),
1032 position: Position::new(CId::new(0), 1),
1033 item: Either::Item('b')
1034 },
1035 ItemRow {
1036 linked_chunk_id: linked_chunk_id.clone(),
1037 position: Position::new(CId::new(0), 2),
1038 item: Either::Item('c')
1039 },
1040 ItemRow {
1041 linked_chunk_id: linked_chunk_id.clone(),
1042 position: Position::new(CId::new(1), 0),
1043 item: Either::Item('x')
1044 },
1045 ItemRow {
1046 linked_chunk_id: linked_chunk_id.clone(),
1047 position: Position::new(CId::new(1), 1),
1048 item: Either::Item('y')
1049 },
1050 ItemRow {
1051 linked_chunk_id: linked_chunk_id.clone(),
1052 position: Position::new(CId::new(1), 2),
1053 item: Either::Item('z')
1054 },
1055 ItemRow {
1056 linked_chunk_id: linked_chunk_id.clone(),
1057 position: Position::new(CId::new(0), 3),
1058 item: Either::Item('d')
1059 },
1060 ItemRow {
1061 linked_chunk_id: linked_chunk_id.clone(),
1062 position: Position::new(CId::new(0), 4),
1063 item: Either::Item('e')
1064 },
1065 ],
1066 );
1067 }
1068
1069 #[test]
1070 fn test_remove_item() {
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', 'd', 'e'],
1087 },
1088 Update::RemoveItem { at: Position::new(CId::new(0), 0) },
1090 Update::RemoveItem { at: Position::new(CId::new(0), 2) },
1092 ],
1093 )
1094 .unwrap();
1095
1096 assert_eq!(
1098 relational_linked_chunk.chunks,
1099 &[ChunkRow {
1100 linked_chunk_id: linked_chunk_id.clone(),
1101 previous_chunk: None,
1102 chunk: CId::new(0),
1103 next_chunk: None
1104 }],
1105 );
1106 assert_eq!(
1108 relational_linked_chunk.items_chunks,
1109 &[
1110 ItemRow {
1111 linked_chunk_id: linked_chunk_id.clone(),
1112 position: Position::new(CId::new(0), 0),
1113 item: Either::Item('b')
1114 },
1115 ItemRow {
1116 linked_chunk_id: linked_chunk_id.clone(),
1117 position: Position::new(CId::new(0), 1),
1118 item: Either::Item('c')
1119 },
1120 ItemRow {
1121 linked_chunk_id: linked_chunk_id.clone(),
1122 position: Position::new(CId::new(0), 2),
1123 item: Either::Item('e')
1124 },
1125 ],
1126 );
1127 }
1128
1129 #[test]
1130 fn test_detach_last_items() {
1131 let room_id = room_id!("!r0:matrix.org");
1132 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
1133
1134 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
1135
1136 relational_linked_chunk
1137 .apply_updates(
1138 linked_chunk_id.as_ref(),
1139 vec![
1140 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1142 Update::NewItemsChunk {
1144 previous: Some(CId::new(0)),
1145 new: CId::new(1),
1146 next: None,
1147 },
1148 Update::PushItems {
1150 at: Position::new(CId::new(0), 0),
1151 items: vec!['a', 'b', 'c', 'd', 'e'],
1152 },
1153 Update::PushItems {
1155 at: Position::new(CId::new(1), 0),
1156 items: vec!['x', 'y', 'z'],
1157 },
1158 Update::DetachLastItems { at: Position::new(CId::new(0), 2) },
1160 ],
1161 )
1162 .unwrap();
1163
1164 assert_eq!(
1166 relational_linked_chunk.chunks,
1167 &[
1168 ChunkRow {
1169 linked_chunk_id: linked_chunk_id.clone(),
1170 previous_chunk: None,
1171 chunk: CId::new(0),
1172 next_chunk: Some(CId::new(1))
1173 },
1174 ChunkRow {
1175 linked_chunk_id: linked_chunk_id.clone(),
1176 previous_chunk: Some(CId::new(0)),
1177 chunk: CId::new(1),
1178 next_chunk: None
1179 },
1180 ],
1181 );
1182 assert_eq!(
1184 relational_linked_chunk.items_chunks,
1185 &[
1186 ItemRow {
1187 linked_chunk_id: linked_chunk_id.clone(),
1188 position: Position::new(CId::new(0), 0),
1189 item: Either::Item('a')
1190 },
1191 ItemRow {
1192 linked_chunk_id: linked_chunk_id.clone(),
1193 position: Position::new(CId::new(0), 1),
1194 item: Either::Item('b')
1195 },
1196 ItemRow {
1197 linked_chunk_id: linked_chunk_id.clone(),
1198 position: Position::new(CId::new(1), 0),
1199 item: Either::Item('x')
1200 },
1201 ItemRow {
1202 linked_chunk_id: linked_chunk_id.clone(),
1203 position: Position::new(CId::new(1), 1),
1204 item: Either::Item('y')
1205 },
1206 ItemRow {
1207 linked_chunk_id: linked_chunk_id.clone(),
1208 position: Position::new(CId::new(1), 2),
1209 item: Either::Item('z')
1210 },
1211 ],
1212 );
1213 }
1214
1215 #[test]
1216 fn test_start_and_end_reattach_items() {
1217 let room_id = room_id!("!r0:matrix.org");
1218 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
1219
1220 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
1221
1222 relational_linked_chunk
1223 .apply_updates(
1224 linked_chunk_id.as_ref(),
1225 vec![Update::StartReattachItems, Update::EndReattachItems],
1226 )
1227 .unwrap();
1228
1229 assert!(relational_linked_chunk.chunks.is_empty());
1231 assert!(relational_linked_chunk.items_chunks.is_empty());
1232 }
1233
1234 #[test]
1235 fn test_clear() {
1236 let r0 = room_id!("!r0:matrix.org");
1237 let linked_chunk_id0 = OwnedLinkedChunkId::Room(r0.to_owned());
1238
1239 let r1 = room_id!("!r1:matrix.org");
1240 let linked_chunk_id1 = OwnedLinkedChunkId::Room(r1.to_owned());
1241
1242 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
1243
1244 relational_linked_chunk
1245 .apply_updates(
1246 linked_chunk_id0.as_ref(),
1247 vec![
1248 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1251 Update::PushItems {
1253 at: Position::new(CId::new(0), 0),
1254 items: vec!['a', 'b', 'c'],
1255 },
1256 ],
1257 )
1258 .unwrap();
1259
1260 relational_linked_chunk
1261 .apply_updates(
1262 linked_chunk_id1.as_ref(),
1263 vec![
1264 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1267 Update::PushItems { at: Position::new(CId::new(0), 0), items: vec!['x'] },
1269 ],
1270 )
1271 .unwrap();
1272
1273 assert_eq!(
1275 relational_linked_chunk.chunks,
1276 &[
1277 ChunkRow {
1278 linked_chunk_id: linked_chunk_id0.to_owned(),
1279 previous_chunk: None,
1280 chunk: CId::new(0),
1281 next_chunk: None,
1282 },
1283 ChunkRow {
1284 linked_chunk_id: linked_chunk_id1.to_owned(),
1285 previous_chunk: None,
1286 chunk: CId::new(0),
1287 next_chunk: None,
1288 }
1289 ],
1290 );
1291
1292 assert_eq!(
1294 relational_linked_chunk.items_chunks,
1295 &[
1296 ItemRow {
1297 linked_chunk_id: linked_chunk_id0.to_owned(),
1298 position: Position::new(CId::new(0), 0),
1299 item: Either::Item('a')
1300 },
1301 ItemRow {
1302 linked_chunk_id: linked_chunk_id0.to_owned(),
1303 position: Position::new(CId::new(0), 1),
1304 item: Either::Item('b')
1305 },
1306 ItemRow {
1307 linked_chunk_id: linked_chunk_id0.to_owned(),
1308 position: Position::new(CId::new(0), 2),
1309 item: Either::Item('c')
1310 },
1311 ItemRow {
1312 linked_chunk_id: linked_chunk_id1.to_owned(),
1313 position: Position::new(CId::new(0), 0),
1314 item: Either::Item('x')
1315 },
1316 ],
1317 );
1318
1319 relational_linked_chunk
1321 .apply_updates(linked_chunk_id0.as_ref(), vec![Update::Clear])
1322 .unwrap();
1323
1324 assert_eq!(
1326 relational_linked_chunk.chunks,
1327 &[ChunkRow {
1328 linked_chunk_id: linked_chunk_id1.to_owned(),
1329 previous_chunk: None,
1330 chunk: CId::new(0),
1331 next_chunk: None,
1332 }],
1333 );
1334
1335 assert_eq!(
1336 relational_linked_chunk.items_chunks,
1337 &[ItemRow {
1338 linked_chunk_id: linked_chunk_id1.to_owned(),
1339 position: Position::new(CId::new(0), 0),
1340 item: Either::Item('x')
1341 },],
1342 );
1343 }
1344
1345 #[test]
1346 fn test_load_empty_linked_chunk() {
1347 let room_id = room_id!("!r0:matrix.org");
1348 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
1349
1350 let relational_linked_chunk = RelationalLinkedChunk::<_, char, char>::new();
1352 let result = relational_linked_chunk.load_all_chunks(linked_chunk_id.as_ref()).unwrap();
1353 assert!(result.is_empty());
1354 }
1355
1356 #[test]
1357 fn test_load_all_chunks_with_empty_items() {
1358 let room_id = room_id!("!r0:matrix.org");
1359 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
1360
1361 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, char>::new();
1362
1363 relational_linked_chunk
1365 .apply_updates(
1366 linked_chunk_id.as_ref(),
1367 vec![Update::NewItemsChunk { previous: None, new: CId::new(0), next: None }],
1368 )
1369 .unwrap();
1370
1371 let lc = from_all_chunks::<3, _, _>(
1373 relational_linked_chunk.load_all_chunks(linked_chunk_id.as_ref()).unwrap(),
1374 )
1375 .expect("building succeeds")
1376 .expect("this leads to a non-empty linked chunk");
1377
1378 assert_items_eq!(lc, []);
1379 }
1380
1381 #[test]
1382 fn test_rebuild_linked_chunk() {
1383 let room_id = room_id!("!r0:matrix.org");
1384 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
1385
1386 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, char>::new();
1387
1388 relational_linked_chunk
1389 .apply_updates(
1390 linked_chunk_id.as_ref(),
1391 vec![
1392 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1394 Update::PushItems {
1396 at: Position::new(CId::new(0), 0),
1397 items: vec!['a', 'b', 'c'],
1398 },
1399 Update::NewGapChunk {
1401 previous: Some(CId::new(0)),
1402 new: CId::new(1),
1403 next: None,
1404 gap: 'g',
1405 },
1406 Update::NewItemsChunk {
1408 previous: Some(CId::new(1)),
1409 new: CId::new(2),
1410 next: None,
1411 },
1412 Update::PushItems {
1414 at: Position::new(CId::new(2), 0),
1415 items: vec!['d', 'e', 'f'],
1416 },
1417 ],
1418 )
1419 .unwrap();
1420
1421 let lc = from_all_chunks::<3, _, _>(
1422 relational_linked_chunk.load_all_chunks(linked_chunk_id.as_ref()).unwrap(),
1423 )
1424 .expect("building succeeds")
1425 .expect("this leads to a non-empty linked chunk");
1426
1427 assert_items_eq!(lc, ['a', 'b', 'c'] [-] ['d', 'e', 'f']);
1429 }
1430
1431 #[test]
1432 fn test_replace_item() {
1433 let room_id = room_id!("!r0:matrix.org");
1434 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
1435
1436 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
1437
1438 relational_linked_chunk
1439 .apply_updates(
1440 linked_chunk_id.as_ref(),
1441 vec![
1442 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1445 Update::PushItems {
1447 at: Position::new(CId::new(0), 0),
1448 items: vec!['a', 'b', 'c'],
1449 },
1450 Update::ReplaceItem { at: Position::new(CId::new(0), 1), item: 'B' },
1452 ],
1453 )
1454 .unwrap();
1455
1456 assert_eq!(
1458 relational_linked_chunk.chunks,
1459 &[ChunkRow {
1460 linked_chunk_id: linked_chunk_id.clone(),
1461 previous_chunk: None,
1462 chunk: CId::new(0),
1463 next_chunk: None,
1464 },],
1465 );
1466
1467 assert_eq!(
1469 relational_linked_chunk.items_chunks,
1470 &[
1471 ItemRow {
1472 linked_chunk_id: linked_chunk_id.clone(),
1473 position: Position::new(CId::new(0), 0),
1474 item: Either::Item('a')
1475 },
1476 ItemRow {
1477 linked_chunk_id: linked_chunk_id.clone(),
1478 position: Position::new(CId::new(0), 1),
1479 item: Either::Item('B')
1480 },
1481 ItemRow {
1482 linked_chunk_id,
1483 position: Position::new(CId::new(0), 2),
1484 item: Either::Item('c')
1485 },
1486 ],
1487 );
1488 }
1489
1490 #[test]
1491 fn test_unordered_events() {
1492 let room_id = room_id!("!r0:matrix.org");
1493 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
1494
1495 let other_room_id = room_id!("!r1:matrix.org");
1496 let other_linked_chunk_id = OwnedLinkedChunkId::Room(other_room_id.to_owned());
1497
1498 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
1499
1500 relational_linked_chunk
1501 .apply_updates(
1502 linked_chunk_id.as_ref(),
1503 vec![
1504 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1505 Update::PushItems {
1506 at: Position::new(CId::new(0), 0),
1507 items: vec!['a', 'b', 'c'],
1508 },
1509 Update::NewItemsChunk {
1510 previous: Some(CId::new(0)),
1511 new: CId::new(1),
1512 next: None,
1513 },
1514 Update::PushItems {
1515 at: Position::new(CId::new(1), 0),
1516 items: vec!['d', 'e', 'f'],
1517 },
1518 ],
1519 )
1520 .unwrap();
1521
1522 relational_linked_chunk
1523 .apply_updates(
1524 other_linked_chunk_id.as_ref(),
1525 vec![
1526 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1527 Update::PushItems {
1528 at: Position::new(CId::new(0), 0),
1529 items: vec!['x', 'y', 'z'],
1530 },
1531 ],
1532 )
1533 .unwrap();
1534
1535 let events = BTreeMap::from_iter(
1536 relational_linked_chunk.unordered_linked_chunk_items(&linked_chunk_id),
1537 );
1538
1539 assert_eq!(events.len(), 6);
1540 assert_eq!(*events.get(&'a').unwrap(), Position::new(CId::new(0), 0));
1541 assert_eq!(*events.get(&'b').unwrap(), Position::new(CId::new(0), 1));
1542 assert_eq!(*events.get(&'c').unwrap(), Position::new(CId::new(0), 2));
1543 assert_eq!(*events.get(&'d').unwrap(), Position::new(CId::new(1), 0));
1544 assert_eq!(*events.get(&'e').unwrap(), Position::new(CId::new(1), 1));
1545 assert_eq!(*events.get(&'f').unwrap(), Position::new(CId::new(1), 2));
1546 }
1547
1548 #[test]
1549 fn test_load_last_chunk() {
1550 let room_id = room_id!("!r0:matrix.org");
1551 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
1552
1553 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
1554
1555 {
1557 let (last_chunk, chunk_identifier_generator) =
1558 relational_linked_chunk.load_last_chunk(linked_chunk_id.as_ref()).unwrap();
1559
1560 assert!(last_chunk.is_none());
1561 assert_eq!(chunk_identifier_generator.current(), 0);
1562 }
1563
1564 {
1566 relational_linked_chunk
1567 .apply_updates(
1568 linked_chunk_id.as_ref(),
1569 vec![
1570 Update::NewItemsChunk { previous: None, new: CId::new(42), next: None },
1571 Update::PushItems {
1572 at: Position::new(CId::new(42), 0),
1573 items: vec!['a', 'b'],
1574 },
1575 ],
1576 )
1577 .unwrap();
1578
1579 let (last_chunk, chunk_identifier_generator) =
1580 relational_linked_chunk.load_last_chunk(linked_chunk_id.as_ref()).unwrap();
1581
1582 assert_matches!(last_chunk, Some(last_chunk) => {
1583 assert_eq!(last_chunk.identifier, 42);
1584 assert!(last_chunk.previous.is_none());
1585 assert!(last_chunk.next.is_none());
1586 assert_matches!(last_chunk.content, ChunkContent::Items(items) => {
1587 assert_eq!(items.len(), 2);
1588 assert_eq!(items, &['a', 'b']);
1589 });
1590 });
1591 assert_eq!(chunk_identifier_generator.current(), 42);
1592 }
1593
1594 {
1596 relational_linked_chunk
1597 .apply_updates(
1598 linked_chunk_id.as_ref(),
1599 vec![
1600 Update::NewItemsChunk {
1601 previous: Some(CId::new(42)),
1602 new: CId::new(7),
1603 next: None,
1604 },
1605 Update::PushItems {
1606 at: Position::new(CId::new(7), 0),
1607 items: vec!['c', 'd', 'e'],
1608 },
1609 ],
1610 )
1611 .unwrap();
1612
1613 let (last_chunk, chunk_identifier_generator) =
1614 relational_linked_chunk.load_last_chunk(linked_chunk_id.as_ref()).unwrap();
1615
1616 assert_matches!(last_chunk, Some(last_chunk) => {
1617 assert_eq!(last_chunk.identifier, 7);
1618 assert_matches!(last_chunk.previous, Some(previous) => {
1619 assert_eq!(previous, 42);
1620 });
1621 assert!(last_chunk.next.is_none());
1622 assert_matches!(last_chunk.content, ChunkContent::Items(items) => {
1623 assert_eq!(items.len(), 3);
1624 assert_eq!(items, &['c', 'd', 'e']);
1625 });
1626 });
1627 assert_eq!(chunk_identifier_generator.current(), 42);
1628 }
1629 }
1630
1631 #[test]
1632 fn test_load_last_chunk_with_a_cycle() {
1633 let room_id = room_id!("!r0:matrix.org");
1634 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
1635 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
1636
1637 relational_linked_chunk
1638 .apply_updates(
1639 linked_chunk_id.as_ref(),
1640 vec![
1641 Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
1642 Update::NewItemsChunk {
1643 previous: Some(CId::new(0)),
1647 new: CId::new(1),
1648 next: Some(CId::new(0)),
1649 },
1650 ],
1651 )
1652 .unwrap();
1653
1654 relational_linked_chunk.load_last_chunk(linked_chunk_id.as_ref()).unwrap_err();
1655 }
1656
1657 #[test]
1658 fn test_load_previous_chunk() {
1659 let room_id = room_id!("!r0:matrix.org");
1660 let linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
1661 let mut relational_linked_chunk = RelationalLinkedChunk::<_, char, ()>::new();
1662
1663 {
1666 let previous_chunk = relational_linked_chunk
1667 .load_previous_chunk(linked_chunk_id.as_ref(), CId::new(153))
1668 .unwrap();
1669
1670 assert!(previous_chunk.is_none());
1671 }
1672
1673 {
1676 relational_linked_chunk
1677 .apply_updates(
1678 linked_chunk_id.as_ref(),
1679 vec![Update::NewItemsChunk { previous: None, new: CId::new(42), next: None }],
1680 )
1681 .unwrap();
1682
1683 let previous_chunk = relational_linked_chunk
1684 .load_previous_chunk(linked_chunk_id.as_ref(), CId::new(42))
1685 .unwrap();
1686
1687 assert!(previous_chunk.is_none());
1688 }
1689
1690 {
1692 relational_linked_chunk
1693 .apply_updates(
1694 linked_chunk_id.as_ref(),
1695 vec![
1696 Update::NewItemsChunk {
1698 previous: None,
1699 new: CId::new(7),
1700 next: Some(CId::new(42)),
1701 },
1702 Update::PushItems {
1703 at: Position::new(CId::new(7), 0),
1704 items: vec!['a', 'b', 'c'],
1705 },
1706 ],
1707 )
1708 .unwrap();
1709
1710 let previous_chunk = relational_linked_chunk
1711 .load_previous_chunk(linked_chunk_id.as_ref(), CId::new(42))
1712 .unwrap();
1713
1714 assert_matches!(previous_chunk, Some(previous_chunk) => {
1715 assert_eq!(previous_chunk.identifier, 7);
1716 assert!(previous_chunk.previous.is_none());
1717 assert_matches!(previous_chunk.next, Some(next) => {
1718 assert_eq!(next, 42);
1719 });
1720 assert_matches!(previous_chunk.content, ChunkContent::Items(items) => {
1721 assert_eq!(items.len(), 3);
1722 assert_eq!(items, &['a', 'b', 'c']);
1723 });
1724 });
1725 }
1726 }
1727}