1use std::collections::VecDeque;
9
10#[cfg(feature = "tiered-storage")]
11use smallvec::SmallVec;
12
13use crate::types::{EpochId, TxId};
14
15#[derive(Debug, Clone, Copy)]
17pub struct VersionInfo {
18 pub created_epoch: EpochId,
20 pub deleted_epoch: Option<EpochId>,
22 pub created_by: TxId,
24}
25
26impl VersionInfo {
27 #[must_use]
29 pub fn new(created_epoch: EpochId, created_by: TxId) -> Self {
30 Self {
31 created_epoch,
32 deleted_epoch: None,
33 created_by,
34 }
35 }
36
37 pub fn mark_deleted(&mut self, epoch: EpochId) {
39 self.deleted_epoch = Some(epoch);
40 }
41
42 #[inline]
44 #[must_use]
45 pub fn is_visible_at(&self, epoch: EpochId) -> bool {
46 if !self.created_epoch.is_visible_at(epoch) {
49 return false;
50 }
51
52 if let Some(deleted) = self.deleted_epoch {
53 deleted.as_u64() > epoch.as_u64()
55 } else {
56 true
57 }
58 }
59
60 #[inline]
67 #[must_use]
68 pub fn is_visible_to(&self, viewing_epoch: EpochId, viewing_tx: TxId) -> bool {
69 if self.created_by == viewing_tx {
71 return self.deleted_epoch.is_none();
72 }
73
74 self.is_visible_at(viewing_epoch)
76 }
77}
78
79#[derive(Debug, Clone)]
81pub struct Version<T> {
82 pub info: VersionInfo,
84 pub data: T,
86}
87
88impl<T> Version<T> {
89 #[must_use]
91 pub fn new(data: T, created_epoch: EpochId, created_by: TxId) -> Self {
92 Self {
93 info: VersionInfo::new(created_epoch, created_by),
94 data,
95 }
96 }
97}
98
99#[derive(Debug, Clone)]
105pub struct VersionChain<T> {
106 versions: VecDeque<Version<T>>,
108}
109
110impl<T> VersionChain<T> {
111 #[must_use]
113 pub fn new() -> Self {
114 Self {
115 versions: VecDeque::new(),
116 }
117 }
118
119 #[must_use]
121 pub fn with_initial(data: T, created_epoch: EpochId, created_by: TxId) -> Self {
122 let mut chain = Self::new();
123 chain.add_version(data, created_epoch, created_by);
124 chain
125 }
126
127 pub fn add_version(&mut self, data: T, created_epoch: EpochId, created_by: TxId) {
131 let version = Version::new(data, created_epoch, created_by);
132 self.versions.push_front(version);
133 }
134
135 #[inline]
140 #[must_use]
141 pub fn visible_at(&self, epoch: EpochId) -> Option<&T> {
142 self.versions
143 .iter()
144 .find(|v| v.info.is_visible_at(epoch))
145 .map(|v| &v.data)
146 }
147
148 #[inline]
152 #[must_use]
153 pub fn visible_to(&self, epoch: EpochId, tx: TxId) -> Option<&T> {
154 self.versions
155 .iter()
156 .find(|v| v.info.is_visible_to(epoch, tx))
157 .map(|v| &v.data)
158 }
159
160 pub fn mark_deleted(&mut self, delete_epoch: EpochId) -> bool {
164 for version in &mut self.versions {
165 if version.info.deleted_epoch.is_none() {
166 version.info.mark_deleted(delete_epoch);
167 return true;
168 }
169 }
170 false
171 }
172
173 #[must_use]
175 pub fn modified_by(&self, tx: TxId) -> bool {
176 self.versions.iter().any(|v| v.info.created_by == tx)
177 }
178
179 pub fn remove_versions_by(&mut self, tx: TxId) {
183 self.versions.retain(|v| v.info.created_by != tx);
184 }
185
186 #[must_use]
191 pub fn has_conflict(&self, start_epoch: EpochId, our_tx: TxId) -> bool {
192 self.versions.iter().any(|v| {
193 v.info.created_by != our_tx && v.info.created_epoch.as_u64() > start_epoch.as_u64()
194 })
195 }
196
197 #[must_use]
199 pub fn version_count(&self) -> usize {
200 self.versions.len()
201 }
202
203 #[must_use]
205 pub fn is_empty(&self) -> bool {
206 self.versions.is_empty()
207 }
208
209 pub fn gc(&mut self, min_epoch: EpochId) {
213 if self.versions.is_empty() {
214 return;
215 }
216
217 let mut keep_count = 0;
218 let mut found_old_visible = false;
219
220 for (i, version) in self.versions.iter().enumerate() {
221 if version.info.created_epoch.as_u64() >= min_epoch.as_u64() {
222 keep_count = i + 1;
223 } else if !found_old_visible {
224 found_old_visible = true;
226 keep_count = i + 1;
227 }
228 }
229
230 self.versions.truncate(keep_count);
231 }
232
233 #[must_use]
235 pub fn latest(&self) -> Option<&T> {
236 self.versions.front().map(|v| &v.data)
237 }
238
239 #[must_use]
241 pub fn latest_mut(&mut self) -> Option<&mut T> {
242 self.versions.front_mut().map(|v| &mut v.data)
243 }
244}
245
246impl<T> Default for VersionChain<T> {
247 fn default() -> Self {
248 Self::new()
249 }
250}
251
252impl<T: Clone> VersionChain<T> {
253 pub fn get_mut(&mut self, epoch: EpochId, tx: TxId, modify_epoch: EpochId) -> Option<&mut T> {
258 let visible_idx = self
260 .versions
261 .iter()
262 .position(|v| v.info.is_visible_to(epoch, tx))?;
263
264 let visible = &self.versions[visible_idx];
265
266 if visible.info.created_by == tx {
267 Some(&mut self.versions[visible_idx].data)
269 } else {
270 let new_data = visible.data.clone();
272 self.add_version(new_data, modify_epoch, tx);
273 Some(&mut self.versions[0].data)
274 }
275 }
276}
277
278#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
291#[repr(transparent)]
292#[cfg(feature = "tiered-storage")]
293pub struct OptionalEpochId(u32);
294
295#[cfg(feature = "tiered-storage")]
296impl OptionalEpochId {
297 pub const NONE: Self = Self(u32::MAX);
299
300 #[must_use]
305 pub fn some(epoch: EpochId) -> Self {
306 debug_assert!(
307 epoch.as_u64() < u64::from(u32::MAX),
308 "epoch {} exceeds OptionalEpochId capacity",
309 epoch.as_u64()
310 );
311 Self(epoch.as_u64() as u32)
312 }
313
314 #[inline]
316 #[must_use]
317 pub fn get(self) -> Option<EpochId> {
318 if self.0 == u32::MAX {
319 None
320 } else {
321 Some(EpochId::new(u64::from(self.0)))
322 }
323 }
324
325 #[must_use]
327 pub fn is_some(self) -> bool {
328 self.0 != u32::MAX
329 }
330
331 #[inline]
333 #[must_use]
334 pub fn is_none(self) -> bool {
335 self.0 == u32::MAX
336 }
337}
338
339#[derive(Debug, Clone, Copy, PartialEq, Eq)]
351#[cfg(feature = "tiered-storage")]
352pub struct HotVersionRef {
353 pub epoch: EpochId,
355 pub arena_offset: u32,
357 pub created_by: TxId,
359 pub deleted_epoch: OptionalEpochId,
361}
362
363#[cfg(feature = "tiered-storage")]
364impl HotVersionRef {
365 #[must_use]
367 pub fn new(epoch: EpochId, arena_offset: u32, created_by: TxId) -> Self {
368 Self {
369 epoch,
370 arena_offset,
371 created_by,
372 deleted_epoch: OptionalEpochId::NONE,
373 }
374 }
375
376 #[inline]
378 #[must_use]
379 pub fn is_visible_at(&self, viewing_epoch: EpochId) -> bool {
380 if !self.epoch.is_visible_at(viewing_epoch) {
382 return false;
383 }
384 match self.deleted_epoch.get() {
386 Some(deleted) => deleted.as_u64() > viewing_epoch.as_u64(),
387 None => true,
388 }
389 }
390
391 #[inline]
393 #[must_use]
394 pub fn is_visible_to(&self, viewing_epoch: EpochId, viewing_tx: TxId) -> bool {
395 if self.created_by == viewing_tx {
397 return self.deleted_epoch.is_none();
398 }
399 self.is_visible_at(viewing_epoch)
401 }
402}
403
404#[derive(Debug, Clone, Copy, PartialEq, Eq)]
417#[cfg(feature = "tiered-storage")]
418pub struct ColdVersionRef {
419 pub epoch: EpochId,
421 pub block_offset: u32,
423 pub length: u16,
425 pub created_by: TxId,
427 pub deleted_epoch: OptionalEpochId,
429}
430
431#[cfg(feature = "tiered-storage")]
432impl ColdVersionRef {
433 #[inline]
435 #[must_use]
436 pub fn is_visible_at(&self, viewing_epoch: EpochId) -> bool {
437 if !self.epoch.is_visible_at(viewing_epoch) {
438 return false;
439 }
440 match self.deleted_epoch.get() {
441 Some(deleted) => deleted.as_u64() > viewing_epoch.as_u64(),
442 None => true,
443 }
444 }
445
446 #[inline]
448 #[must_use]
449 pub fn is_visible_to(&self, viewing_epoch: EpochId, viewing_tx: TxId) -> bool {
450 if self.created_by == viewing_tx {
451 return self.deleted_epoch.is_none();
452 }
453 self.is_visible_at(viewing_epoch)
454 }
455}
456
457#[derive(Debug, Clone, Copy)]
459#[cfg(feature = "tiered-storage")]
460pub enum VersionRef {
461 Hot(HotVersionRef),
463 Cold(ColdVersionRef),
465}
466
467#[cfg(feature = "tiered-storage")]
468impl VersionRef {
469 #[must_use]
471 pub fn epoch(&self) -> EpochId {
472 match self {
473 Self::Hot(h) => h.epoch,
474 Self::Cold(c) => c.epoch,
475 }
476 }
477
478 #[must_use]
480 pub fn created_by(&self) -> TxId {
481 match self {
482 Self::Hot(h) => h.created_by,
483 Self::Cold(c) => c.created_by,
484 }
485 }
486
487 #[must_use]
489 pub fn is_hot(&self) -> bool {
490 matches!(self, Self::Hot(_))
491 }
492
493 #[must_use]
495 pub fn is_cold(&self) -> bool {
496 matches!(self, Self::Cold(_))
497 }
498}
499
500#[derive(Debug, Clone)]
516#[cfg(feature = "tiered-storage")]
517pub struct VersionIndex {
518 hot: SmallVec<[HotVersionRef; 2]>,
520 cold: SmallVec<[ColdVersionRef; 4]>,
522 latest_epoch: EpochId,
524}
525
526#[cfg(feature = "tiered-storage")]
527impl VersionIndex {
528 #[must_use]
530 pub fn new() -> Self {
531 Self {
532 hot: SmallVec::new(),
533 cold: SmallVec::new(),
534 latest_epoch: EpochId::INITIAL,
535 }
536 }
537
538 #[must_use]
540 pub fn with_initial(hot_ref: HotVersionRef) -> Self {
541 let mut index = Self::new();
542 index.add_hot(hot_ref);
543 index
544 }
545
546 pub fn add_hot(&mut self, hot_ref: HotVersionRef) {
548 self.hot.insert(0, hot_ref);
550 self.latest_epoch = hot_ref.epoch;
551 }
552
553 #[must_use]
555 pub fn latest_epoch(&self) -> EpochId {
556 self.latest_epoch
557 }
558
559 #[must_use]
561 pub fn is_empty(&self) -> bool {
562 self.hot.is_empty() && self.cold.is_empty()
563 }
564
565 #[must_use]
567 pub fn version_count(&self) -> usize {
568 self.hot.len() + self.cold.len()
569 }
570
571 #[must_use]
573 pub fn hot_count(&self) -> usize {
574 self.hot.len()
575 }
576
577 #[must_use]
579 pub fn cold_count(&self) -> usize {
580 self.cold.len()
581 }
582
583 #[inline]
585 #[must_use]
586 pub fn visible_at(&self, epoch: EpochId) -> Option<VersionRef> {
587 for v in &self.hot {
589 if v.is_visible_at(epoch) {
590 return Some(VersionRef::Hot(*v));
591 }
592 }
593 for v in &self.cold {
595 if v.is_visible_at(epoch) {
596 return Some(VersionRef::Cold(*v));
597 }
598 }
599 None
600 }
601
602 #[inline]
604 #[must_use]
605 pub fn visible_to(&self, epoch: EpochId, tx: TxId) -> Option<VersionRef> {
606 for v in &self.hot {
608 if v.is_visible_to(epoch, tx) {
609 return Some(VersionRef::Hot(*v));
610 }
611 }
612 for v in &self.cold {
614 if v.is_visible_to(epoch, tx) {
615 return Some(VersionRef::Cold(*v));
616 }
617 }
618 None
619 }
620
621 pub fn mark_deleted(&mut self, delete_epoch: EpochId) -> bool {
625 for v in &mut self.hot {
627 if v.deleted_epoch.is_none() {
628 v.deleted_epoch = OptionalEpochId::some(delete_epoch);
629 return true;
630 }
631 }
632 for v in &mut self.cold {
634 if v.deleted_epoch.is_none() {
635 v.deleted_epoch = OptionalEpochId::some(delete_epoch);
636 return true;
637 }
638 }
639 false
640 }
641
642 #[must_use]
644 pub fn modified_by(&self, tx: TxId) -> bool {
645 self.hot.iter().any(|v| v.created_by == tx) || self.cold.iter().any(|v| v.created_by == tx)
646 }
647
648 pub fn remove_versions_by(&mut self, tx: TxId) {
650 self.hot.retain(|v| v.created_by != tx);
651 self.cold.retain(|v| v.created_by != tx);
652 self.recalculate_latest_epoch();
653 }
654
655 #[must_use]
660 pub fn has_conflict(&self, start_epoch: EpochId, our_tx: TxId) -> bool {
661 self.hot
662 .iter()
663 .any(|v| v.created_by != our_tx && v.epoch.as_u64() > start_epoch.as_u64())
664 || self
665 .cold
666 .iter()
667 .any(|v| v.created_by != our_tx && v.epoch.as_u64() > start_epoch.as_u64())
668 }
669
670 pub fn gc(&mut self, min_epoch: EpochId) {
674 if self.is_empty() {
675 return;
676 }
677
678 let mut found_old_visible = false;
682
683 self.hot.retain(|v| {
684 if v.epoch.as_u64() >= min_epoch.as_u64() {
685 true
686 } else if !found_old_visible {
687 found_old_visible = true;
688 true
689 } else {
690 false
691 }
692 });
693
694 if !found_old_visible {
696 self.cold.retain(|v| {
697 if v.epoch.as_u64() >= min_epoch.as_u64() {
698 true
699 } else if !found_old_visible {
700 found_old_visible = true;
701 true
702 } else {
703 false
704 }
705 });
706 } else {
707 self.cold.retain(|v| v.epoch.as_u64() >= min_epoch.as_u64());
709 }
710 }
711
712 #[must_use]
714 pub fn latest(&self) -> Option<VersionRef> {
715 self.hot
716 .first()
717 .map(|v| VersionRef::Hot(*v))
718 .or_else(|| self.cold.first().map(|v| VersionRef::Cold(*v)))
719 }
720
721 pub fn freeze_epoch(
726 &mut self,
727 epoch: EpochId,
728 cold_refs: impl Iterator<Item = ColdVersionRef>,
729 ) {
730 self.hot.retain(|v| v.epoch != epoch);
732
733 self.cold.extend(cold_refs);
735
736 self.cold
738 .sort_by(|a, b| b.epoch.as_u64().cmp(&a.epoch.as_u64()));
739
740 self.recalculate_latest_epoch();
741 }
742
743 pub fn hot_refs_for_epoch(&self, epoch: EpochId) -> impl Iterator<Item = &HotVersionRef> {
745 self.hot.iter().filter(move |v| v.epoch == epoch)
746 }
747
748 #[must_use]
750 pub fn hot_spilled(&self) -> bool {
751 self.hot.spilled()
752 }
753
754 #[must_use]
756 pub fn cold_spilled(&self) -> bool {
757 self.cold.spilled()
758 }
759
760 fn recalculate_latest_epoch(&mut self) {
761 self.latest_epoch = self
762 .hot
763 .first()
764 .map(|v| v.epoch)
765 .or_else(|| self.cold.first().map(|v| v.epoch))
766 .unwrap_or(EpochId::INITIAL);
767 }
768}
769
770#[cfg(feature = "tiered-storage")]
771impl Default for VersionIndex {
772 fn default() -> Self {
773 Self::new()
774 }
775}
776
777#[cfg(test)]
778mod tests {
779 use super::*;
780
781 #[test]
782 fn test_version_visibility() {
783 let v = VersionInfo::new(EpochId::new(5), TxId::new(1));
784
785 assert!(!v.is_visible_at(EpochId::new(4)));
787
788 assert!(v.is_visible_at(EpochId::new(5)));
790 assert!(v.is_visible_at(EpochId::new(10)));
791 }
792
793 #[test]
794 fn test_deleted_version_visibility() {
795 let mut v = VersionInfo::new(EpochId::new(5), TxId::new(1));
796 v.mark_deleted(EpochId::new(10));
797
798 assert!(v.is_visible_at(EpochId::new(5)));
800 assert!(v.is_visible_at(EpochId::new(9)));
801
802 assert!(!v.is_visible_at(EpochId::new(10)));
804 assert!(!v.is_visible_at(EpochId::new(15)));
805 }
806
807 #[test]
808 fn test_version_visibility_to_transaction() {
809 let v = VersionInfo::new(EpochId::new(5), TxId::new(1));
810
811 assert!(v.is_visible_to(EpochId::new(3), TxId::new(1)));
813
814 assert!(!v.is_visible_to(EpochId::new(3), TxId::new(2)));
816 assert!(v.is_visible_to(EpochId::new(5), TxId::new(2)));
817 }
818
819 #[test]
820 fn test_version_chain_basic() {
821 let mut chain = VersionChain::with_initial("v1", EpochId::new(1), TxId::new(1));
822
823 assert_eq!(chain.visible_at(EpochId::new(1)), Some(&"v1"));
825 assert_eq!(chain.visible_at(EpochId::new(0)), None);
826
827 chain.add_version("v2", EpochId::new(5), TxId::new(2));
829
830 assert_eq!(chain.visible_at(EpochId::new(1)), Some(&"v1"));
832 assert_eq!(chain.visible_at(EpochId::new(4)), Some(&"v1"));
833 assert_eq!(chain.visible_at(EpochId::new(5)), Some(&"v2"));
834 assert_eq!(chain.visible_at(EpochId::new(10)), Some(&"v2"));
835 }
836
837 #[test]
838 fn test_version_chain_rollback() {
839 let mut chain = VersionChain::with_initial("v1", EpochId::new(1), TxId::new(1));
840 chain.add_version("v2", EpochId::new(5), TxId::new(2));
841 chain.add_version("v3", EpochId::new(6), TxId::new(2));
842
843 assert_eq!(chain.version_count(), 3);
844
845 chain.remove_versions_by(TxId::new(2));
847
848 assert_eq!(chain.version_count(), 1);
849 assert_eq!(chain.visible_at(EpochId::new(10)), Some(&"v1"));
850 }
851
852 #[test]
853 fn test_version_chain_deletion() {
854 let mut chain = VersionChain::with_initial("v1", EpochId::new(1), TxId::new(1));
855
856 assert!(chain.mark_deleted(EpochId::new(5)));
858
859 assert_eq!(chain.visible_at(EpochId::new(4)), Some(&"v1"));
861 assert_eq!(chain.visible_at(EpochId::new(5)), None);
862 assert_eq!(chain.visible_at(EpochId::new(10)), None);
863 }
864}
865
866#[cfg(all(test, feature = "tiered-storage"))]
871mod tiered_storage_tests {
872 use super::*;
873
874 #[test]
875 fn test_optional_epoch_id() {
876 let none = OptionalEpochId::NONE;
878 assert!(none.is_none());
879 assert!(!none.is_some());
880 assert_eq!(none.get(), None);
881
882 let some = OptionalEpochId::some(EpochId::new(42));
884 assert!(some.is_some());
885 assert!(!some.is_none());
886 assert_eq!(some.get(), Some(EpochId::new(42)));
887
888 let zero = OptionalEpochId::some(EpochId::new(0));
890 assert!(zero.is_some());
891 assert_eq!(zero.get(), Some(EpochId::new(0)));
892 }
893
894 #[test]
895 fn test_hot_version_ref_visibility() {
896 let hot = HotVersionRef::new(EpochId::new(5), 100, TxId::new(1));
897
898 assert!(!hot.is_visible_at(EpochId::new(4)));
900
901 assert!(hot.is_visible_at(EpochId::new(5)));
903 assert!(hot.is_visible_at(EpochId::new(10)));
904 }
905
906 #[test]
907 fn test_hot_version_ref_deleted_visibility() {
908 let mut hot = HotVersionRef::new(EpochId::new(5), 100, TxId::new(1));
909 hot.deleted_epoch = OptionalEpochId::some(EpochId::new(10));
910
911 assert!(hot.is_visible_at(EpochId::new(5)));
913 assert!(hot.is_visible_at(EpochId::new(9)));
914
915 assert!(!hot.is_visible_at(EpochId::new(10)));
917 assert!(!hot.is_visible_at(EpochId::new(15)));
918 }
919
920 #[test]
921 fn test_hot_version_ref_transaction_visibility() {
922 let hot = HotVersionRef::new(EpochId::new(5), 100, TxId::new(1));
923
924 assert!(hot.is_visible_to(EpochId::new(3), TxId::new(1)));
926
927 assert!(!hot.is_visible_to(EpochId::new(3), TxId::new(2)));
929 assert!(hot.is_visible_to(EpochId::new(5), TxId::new(2)));
930 }
931
932 #[test]
933 fn test_version_index_basic() {
934 let hot = HotVersionRef::new(EpochId::new(1), 0, TxId::new(1));
935 let mut index = VersionIndex::with_initial(hot);
936
937 assert!(index.visible_at(EpochId::new(1)).is_some());
939 assert!(index.visible_at(EpochId::new(0)).is_none());
940
941 let hot2 = HotVersionRef::new(EpochId::new(5), 100, TxId::new(2));
943 index.add_hot(hot2);
944
945 let v1 = index.visible_at(EpochId::new(4)).unwrap();
947 assert!(matches!(v1, VersionRef::Hot(h) if h.arena_offset == 0));
948
949 let v2 = index.visible_at(EpochId::new(5)).unwrap();
950 assert!(matches!(v2, VersionRef::Hot(h) if h.arena_offset == 100));
951 }
952
953 #[test]
954 fn test_version_index_deletion() {
955 let hot = HotVersionRef::new(EpochId::new(1), 0, TxId::new(1));
956 let mut index = VersionIndex::with_initial(hot);
957
958 assert!(index.mark_deleted(EpochId::new(5)));
960
961 assert!(index.visible_at(EpochId::new(4)).is_some());
963 assert!(index.visible_at(EpochId::new(5)).is_none());
964 assert!(index.visible_at(EpochId::new(10)).is_none());
965 }
966
967 #[test]
968 fn test_version_index_transaction_visibility() {
969 let tx = TxId::new(10);
970 let hot = HotVersionRef::new(EpochId::new(5), 0, tx);
971 let index = VersionIndex::with_initial(hot);
972
973 assert!(index.visible_to(EpochId::new(3), tx).is_some());
975
976 assert!(index.visible_to(EpochId::new(3), TxId::new(20)).is_none());
978 assert!(index.visible_to(EpochId::new(5), TxId::new(20)).is_some());
979 }
980
981 #[test]
982 fn test_version_index_rollback() {
983 let tx1 = TxId::new(10);
984 let tx2 = TxId::new(20);
985
986 let mut index = VersionIndex::new();
987 index.add_hot(HotVersionRef::new(EpochId::new(1), 0, tx1));
988 index.add_hot(HotVersionRef::new(EpochId::new(2), 100, tx2));
989 index.add_hot(HotVersionRef::new(EpochId::new(3), 200, tx2));
990
991 assert_eq!(index.version_count(), 3);
992 assert!(index.modified_by(tx1));
993 assert!(index.modified_by(tx2));
994
995 index.remove_versions_by(tx2);
997
998 assert_eq!(index.version_count(), 1);
999 assert!(index.modified_by(tx1));
1000 assert!(!index.modified_by(tx2));
1001
1002 let v = index.visible_at(EpochId::new(10)).unwrap();
1004 assert!(matches!(v, VersionRef::Hot(h) if h.created_by == tx1));
1005 }
1006
1007 #[test]
1008 fn test_version_index_gc() {
1009 let mut index = VersionIndex::new();
1010
1011 for epoch in [1, 3, 5] {
1013 index.add_hot(HotVersionRef::new(
1014 EpochId::new(epoch),
1015 epoch as u32 * 100,
1016 TxId::new(epoch),
1017 ));
1018 }
1019
1020 assert_eq!(index.version_count(), 3);
1021
1022 index.gc(EpochId::new(4));
1025
1026 assert_eq!(index.version_count(), 2);
1027
1028 assert!(index.visible_at(EpochId::new(5)).is_some());
1030 assert!(index.visible_at(EpochId::new(3)).is_some());
1031 }
1032
1033 #[test]
1034 fn test_version_index_conflict_detection() {
1035 let tx1 = TxId::new(10);
1036 let tx2 = TxId::new(20);
1037
1038 let mut index = VersionIndex::new();
1039 index.add_hot(HotVersionRef::new(EpochId::new(1), 0, tx1));
1040 index.add_hot(HotVersionRef::new(EpochId::new(5), 100, tx2));
1041
1042 assert!(index.has_conflict(EpochId::new(0), tx1));
1044
1045 assert!(index.has_conflict(EpochId::new(0), tx2));
1047
1048 assert!(!index.has_conflict(EpochId::new(5), tx1));
1050
1051 assert!(!index.has_conflict(EpochId::new(1), tx2));
1053
1054 let mut index2 = VersionIndex::new();
1056 index2.add_hot(HotVersionRef::new(EpochId::new(5), 0, tx1));
1057 assert!(!index2.has_conflict(EpochId::new(0), tx1));
1058 }
1059
1060 #[test]
1061 fn test_version_index_smallvec_no_heap() {
1062 let mut index = VersionIndex::new();
1063
1064 for i in 0..2 {
1066 index.add_hot(HotVersionRef::new(EpochId::new(i), i as u32, TxId::new(i)));
1067 }
1068
1069 assert!(!index.hot_spilled());
1071 assert!(!index.cold_spilled());
1072 }
1073
1074 #[test]
1075 fn test_version_index_freeze_epoch() {
1076 let mut index = VersionIndex::new();
1077 index.add_hot(HotVersionRef::new(EpochId::new(1), 0, TxId::new(1)));
1078 index.add_hot(HotVersionRef::new(EpochId::new(2), 100, TxId::new(2)));
1079
1080 assert_eq!(index.hot_count(), 2);
1081 assert_eq!(index.cold_count(), 0);
1082
1083 let cold_ref = ColdVersionRef {
1085 epoch: EpochId::new(1),
1086 block_offset: 0,
1087 length: 32,
1088 created_by: TxId::new(1),
1089 deleted_epoch: OptionalEpochId::NONE,
1090 };
1091 index.freeze_epoch(EpochId::new(1), std::iter::once(cold_ref));
1092
1093 assert_eq!(index.hot_count(), 1);
1095 assert_eq!(index.cold_count(), 1);
1096
1097 assert!(index.visible_at(EpochId::new(1)).is_some());
1099 assert!(index.visible_at(EpochId::new(2)).is_some());
1100
1101 let v1 = index.visible_at(EpochId::new(1)).unwrap();
1103 assert!(v1.is_cold());
1104
1105 let v2 = index.visible_at(EpochId::new(2)).unwrap();
1106 assert!(v2.is_hot());
1107 }
1108
1109 #[test]
1110 fn test_version_ref_accessors() {
1111 let hot = HotVersionRef::new(EpochId::new(5), 100, TxId::new(10));
1112 let vr = VersionRef::Hot(hot);
1113
1114 assert_eq!(vr.epoch(), EpochId::new(5));
1115 assert_eq!(vr.created_by(), TxId::new(10));
1116 assert!(vr.is_hot());
1117 assert!(!vr.is_cold());
1118 }
1119
1120 #[test]
1121 fn test_version_index_latest_epoch() {
1122 let mut index = VersionIndex::new();
1123 assert_eq!(index.latest_epoch(), EpochId::INITIAL);
1124
1125 index.add_hot(HotVersionRef::new(EpochId::new(5), 0, TxId::new(1)));
1126 assert_eq!(index.latest_epoch(), EpochId::new(5));
1127
1128 index.add_hot(HotVersionRef::new(EpochId::new(10), 100, TxId::new(2)));
1129 assert_eq!(index.latest_epoch(), EpochId::new(10));
1130
1131 index.remove_versions_by(TxId::new(2));
1133 assert_eq!(index.latest_epoch(), EpochId::new(5));
1134 }
1135
1136 #[test]
1137 fn test_version_index_default() {
1138 let index = VersionIndex::default();
1139 assert!(index.is_empty());
1140 assert_eq!(index.version_count(), 0);
1141 }
1142
1143 #[test]
1144 fn test_version_index_latest() {
1145 let mut index = VersionIndex::new();
1146 assert!(index.latest().is_none());
1147
1148 index.add_hot(HotVersionRef::new(EpochId::new(1), 0, TxId::new(1)));
1149 let latest = index.latest().unwrap();
1150 assert!(matches!(latest, VersionRef::Hot(h) if h.epoch == EpochId::new(1)));
1151
1152 index.add_hot(HotVersionRef::new(EpochId::new(5), 100, TxId::new(2)));
1153 let latest = index.latest().unwrap();
1154 assert!(matches!(latest, VersionRef::Hot(h) if h.epoch == EpochId::new(5)));
1155 }
1156}