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 assert!(
307 epoch.as_u64() < u64::from(u32::MAX),
308 "epoch {} exceeds OptionalEpochId capacity (max {})",
309 epoch.as_u64(),
310 u32::MAX as u64 - 1
311 );
312 Self(epoch.as_u64() as u32)
313 }
314
315 #[inline]
317 #[must_use]
318 pub fn get(self) -> Option<EpochId> {
319 if self.0 == u32::MAX {
320 None
321 } else {
322 Some(EpochId::new(u64::from(self.0)))
323 }
324 }
325
326 #[must_use]
328 pub fn is_some(self) -> bool {
329 self.0 != u32::MAX
330 }
331
332 #[inline]
334 #[must_use]
335 pub fn is_none(self) -> bool {
336 self.0 == u32::MAX
337 }
338}
339
340#[derive(Debug, Clone, Copy, PartialEq, Eq)]
352#[cfg(feature = "tiered-storage")]
353pub struct HotVersionRef {
354 pub epoch: EpochId,
356 pub arena_offset: u32,
358 pub created_by: TxId,
360 pub deleted_epoch: OptionalEpochId,
362}
363
364#[cfg(feature = "tiered-storage")]
365impl HotVersionRef {
366 #[must_use]
368 pub fn new(epoch: EpochId, arena_offset: u32, created_by: TxId) -> Self {
369 Self {
370 epoch,
371 arena_offset,
372 created_by,
373 deleted_epoch: OptionalEpochId::NONE,
374 }
375 }
376
377 #[inline]
379 #[must_use]
380 pub fn is_visible_at(&self, viewing_epoch: EpochId) -> bool {
381 if !self.epoch.is_visible_at(viewing_epoch) {
383 return false;
384 }
385 match self.deleted_epoch.get() {
387 Some(deleted) => deleted.as_u64() > viewing_epoch.as_u64(),
388 None => true,
389 }
390 }
391
392 #[inline]
394 #[must_use]
395 pub fn is_visible_to(&self, viewing_epoch: EpochId, viewing_tx: TxId) -> bool {
396 if self.created_by == viewing_tx {
398 return self.deleted_epoch.is_none();
399 }
400 self.is_visible_at(viewing_epoch)
402 }
403}
404
405#[derive(Debug, Clone, Copy, PartialEq, Eq)]
418#[cfg(feature = "tiered-storage")]
419pub struct ColdVersionRef {
420 pub epoch: EpochId,
422 pub block_offset: u32,
424 pub length: u16,
426 pub created_by: TxId,
428 pub deleted_epoch: OptionalEpochId,
430}
431
432#[cfg(feature = "tiered-storage")]
433impl ColdVersionRef {
434 #[inline]
436 #[must_use]
437 pub fn is_visible_at(&self, viewing_epoch: EpochId) -> bool {
438 if !self.epoch.is_visible_at(viewing_epoch) {
439 return false;
440 }
441 match self.deleted_epoch.get() {
442 Some(deleted) => deleted.as_u64() > viewing_epoch.as_u64(),
443 None => true,
444 }
445 }
446
447 #[inline]
449 #[must_use]
450 pub fn is_visible_to(&self, viewing_epoch: EpochId, viewing_tx: TxId) -> bool {
451 if self.created_by == viewing_tx {
452 return self.deleted_epoch.is_none();
453 }
454 self.is_visible_at(viewing_epoch)
455 }
456}
457
458#[derive(Debug, Clone, Copy)]
460#[cfg(feature = "tiered-storage")]
461pub enum VersionRef {
462 Hot(HotVersionRef),
464 Cold(ColdVersionRef),
466}
467
468#[cfg(feature = "tiered-storage")]
469impl VersionRef {
470 #[must_use]
472 pub fn epoch(&self) -> EpochId {
473 match self {
474 Self::Hot(h) => h.epoch,
475 Self::Cold(c) => c.epoch,
476 }
477 }
478
479 #[must_use]
481 pub fn created_by(&self) -> TxId {
482 match self {
483 Self::Hot(h) => h.created_by,
484 Self::Cold(c) => c.created_by,
485 }
486 }
487
488 #[must_use]
490 pub fn is_hot(&self) -> bool {
491 matches!(self, Self::Hot(_))
492 }
493
494 #[must_use]
496 pub fn is_cold(&self) -> bool {
497 matches!(self, Self::Cold(_))
498 }
499}
500
501#[derive(Debug, Clone)]
517#[cfg(feature = "tiered-storage")]
518pub struct VersionIndex {
519 hot: SmallVec<[HotVersionRef; 2]>,
521 cold: SmallVec<[ColdVersionRef; 4]>,
523 latest_epoch: EpochId,
525}
526
527#[cfg(feature = "tiered-storage")]
528impl VersionIndex {
529 #[must_use]
531 pub fn new() -> Self {
532 Self {
533 hot: SmallVec::new(),
534 cold: SmallVec::new(),
535 latest_epoch: EpochId::INITIAL,
536 }
537 }
538
539 #[must_use]
541 pub fn with_initial(hot_ref: HotVersionRef) -> Self {
542 let mut index = Self::new();
543 index.add_hot(hot_ref);
544 index
545 }
546
547 pub fn add_hot(&mut self, hot_ref: HotVersionRef) {
549 self.hot.insert(0, hot_ref);
551 self.latest_epoch = hot_ref.epoch;
552 }
553
554 #[must_use]
556 pub fn latest_epoch(&self) -> EpochId {
557 self.latest_epoch
558 }
559
560 #[must_use]
562 pub fn is_empty(&self) -> bool {
563 self.hot.is_empty() && self.cold.is_empty()
564 }
565
566 #[must_use]
568 pub fn version_count(&self) -> usize {
569 self.hot.len() + self.cold.len()
570 }
571
572 #[must_use]
574 pub fn hot_count(&self) -> usize {
575 self.hot.len()
576 }
577
578 #[must_use]
580 pub fn cold_count(&self) -> usize {
581 self.cold.len()
582 }
583
584 #[inline]
586 #[must_use]
587 pub fn visible_at(&self, epoch: EpochId) -> Option<VersionRef> {
588 for v in &self.hot {
590 if v.is_visible_at(epoch) {
591 return Some(VersionRef::Hot(*v));
592 }
593 }
594 for v in &self.cold {
596 if v.is_visible_at(epoch) {
597 return Some(VersionRef::Cold(*v));
598 }
599 }
600 None
601 }
602
603 #[inline]
605 #[must_use]
606 pub fn visible_to(&self, epoch: EpochId, tx: TxId) -> Option<VersionRef> {
607 for v in &self.hot {
609 if v.is_visible_to(epoch, tx) {
610 return Some(VersionRef::Hot(*v));
611 }
612 }
613 for v in &self.cold {
615 if v.is_visible_to(epoch, tx) {
616 return Some(VersionRef::Cold(*v));
617 }
618 }
619 None
620 }
621
622 pub fn mark_deleted(&mut self, delete_epoch: EpochId) -> bool {
626 for v in &mut self.hot {
628 if v.deleted_epoch.is_none() {
629 v.deleted_epoch = OptionalEpochId::some(delete_epoch);
630 return true;
631 }
632 }
633 for v in &mut self.cold {
635 if v.deleted_epoch.is_none() {
636 v.deleted_epoch = OptionalEpochId::some(delete_epoch);
637 return true;
638 }
639 }
640 false
641 }
642
643 #[must_use]
645 pub fn modified_by(&self, tx: TxId) -> bool {
646 self.hot.iter().any(|v| v.created_by == tx) || self.cold.iter().any(|v| v.created_by == tx)
647 }
648
649 pub fn remove_versions_by(&mut self, tx: TxId) {
651 self.hot.retain(|v| v.created_by != tx);
652 self.cold.retain(|v| v.created_by != tx);
653 self.recalculate_latest_epoch();
654 }
655
656 #[must_use]
661 pub fn has_conflict(&self, start_epoch: EpochId, our_tx: TxId) -> bool {
662 self.hot
663 .iter()
664 .any(|v| v.created_by != our_tx && v.epoch.as_u64() > start_epoch.as_u64())
665 || self
666 .cold
667 .iter()
668 .any(|v| v.created_by != our_tx && v.epoch.as_u64() > start_epoch.as_u64())
669 }
670
671 pub fn gc(&mut self, min_epoch: EpochId) {
675 if self.is_empty() {
676 return;
677 }
678
679 let mut found_old_visible = false;
683
684 self.hot.retain(|v| {
685 if v.epoch.as_u64() >= min_epoch.as_u64() {
686 true
687 } else if !found_old_visible {
688 found_old_visible = true;
689 true
690 } else {
691 false
692 }
693 });
694
695 if !found_old_visible {
697 self.cold.retain(|v| {
698 if v.epoch.as_u64() >= min_epoch.as_u64() {
699 true
700 } else if !found_old_visible {
701 found_old_visible = true;
702 true
703 } else {
704 false
705 }
706 });
707 } else {
708 self.cold.retain(|v| v.epoch.as_u64() >= min_epoch.as_u64());
710 }
711 }
712
713 #[must_use]
715 pub fn latest(&self) -> Option<VersionRef> {
716 self.hot
717 .first()
718 .map(|v| VersionRef::Hot(*v))
719 .or_else(|| self.cold.first().map(|v| VersionRef::Cold(*v)))
720 }
721
722 pub fn freeze_epoch(
727 &mut self,
728 epoch: EpochId,
729 cold_refs: impl Iterator<Item = ColdVersionRef>,
730 ) {
731 self.hot.retain(|v| v.epoch != epoch);
733
734 self.cold.extend(cold_refs);
736
737 self.cold
739 .sort_by(|a, b| b.epoch.as_u64().cmp(&a.epoch.as_u64()));
740
741 self.recalculate_latest_epoch();
742 }
743
744 pub fn hot_refs_for_epoch(&self, epoch: EpochId) -> impl Iterator<Item = &HotVersionRef> {
746 self.hot.iter().filter(move |v| v.epoch == epoch)
747 }
748
749 #[must_use]
751 pub fn hot_spilled(&self) -> bool {
752 self.hot.spilled()
753 }
754
755 #[must_use]
757 pub fn cold_spilled(&self) -> bool {
758 self.cold.spilled()
759 }
760
761 fn recalculate_latest_epoch(&mut self) {
762 self.latest_epoch = self
763 .hot
764 .first()
765 .map(|v| v.epoch)
766 .or_else(|| self.cold.first().map(|v| v.epoch))
767 .unwrap_or(EpochId::INITIAL);
768 }
769}
770
771#[cfg(feature = "tiered-storage")]
772impl Default for VersionIndex {
773 fn default() -> Self {
774 Self::new()
775 }
776}
777
778#[cfg(test)]
779mod tests {
780 use super::*;
781
782 #[test]
783 fn test_version_visibility() {
784 let v = VersionInfo::new(EpochId::new(5), TxId::new(1));
785
786 assert!(!v.is_visible_at(EpochId::new(4)));
788
789 assert!(v.is_visible_at(EpochId::new(5)));
791 assert!(v.is_visible_at(EpochId::new(10)));
792 }
793
794 #[test]
795 fn test_deleted_version_visibility() {
796 let mut v = VersionInfo::new(EpochId::new(5), TxId::new(1));
797 v.mark_deleted(EpochId::new(10));
798
799 assert!(v.is_visible_at(EpochId::new(5)));
801 assert!(v.is_visible_at(EpochId::new(9)));
802
803 assert!(!v.is_visible_at(EpochId::new(10)));
805 assert!(!v.is_visible_at(EpochId::new(15)));
806 }
807
808 #[test]
809 fn test_version_visibility_to_transaction() {
810 let v = VersionInfo::new(EpochId::new(5), TxId::new(1));
811
812 assert!(v.is_visible_to(EpochId::new(3), TxId::new(1)));
814
815 assert!(!v.is_visible_to(EpochId::new(3), TxId::new(2)));
817 assert!(v.is_visible_to(EpochId::new(5), TxId::new(2)));
818 }
819
820 #[test]
821 fn test_version_chain_basic() {
822 let mut chain = VersionChain::with_initial("v1", EpochId::new(1), TxId::new(1));
823
824 assert_eq!(chain.visible_at(EpochId::new(1)), Some(&"v1"));
826 assert_eq!(chain.visible_at(EpochId::new(0)), None);
827
828 chain.add_version("v2", EpochId::new(5), TxId::new(2));
830
831 assert_eq!(chain.visible_at(EpochId::new(1)), Some(&"v1"));
833 assert_eq!(chain.visible_at(EpochId::new(4)), Some(&"v1"));
834 assert_eq!(chain.visible_at(EpochId::new(5)), Some(&"v2"));
835 assert_eq!(chain.visible_at(EpochId::new(10)), Some(&"v2"));
836 }
837
838 #[test]
839 fn test_version_chain_rollback() {
840 let mut chain = VersionChain::with_initial("v1", EpochId::new(1), TxId::new(1));
841 chain.add_version("v2", EpochId::new(5), TxId::new(2));
842 chain.add_version("v3", EpochId::new(6), TxId::new(2));
843
844 assert_eq!(chain.version_count(), 3);
845
846 chain.remove_versions_by(TxId::new(2));
848
849 assert_eq!(chain.version_count(), 1);
850 assert_eq!(chain.visible_at(EpochId::new(10)), Some(&"v1"));
851 }
852
853 #[test]
854 fn test_version_chain_deletion() {
855 let mut chain = VersionChain::with_initial("v1", EpochId::new(1), TxId::new(1));
856
857 assert!(chain.mark_deleted(EpochId::new(5)));
859
860 assert_eq!(chain.visible_at(EpochId::new(4)), Some(&"v1"));
862 assert_eq!(chain.visible_at(EpochId::new(5)), None);
863 assert_eq!(chain.visible_at(EpochId::new(10)), None);
864 }
865}
866
867#[cfg(all(test, feature = "tiered-storage"))]
872mod tiered_storage_tests {
873 use super::*;
874
875 #[test]
876 fn test_optional_epoch_id() {
877 let none = OptionalEpochId::NONE;
879 assert!(none.is_none());
880 assert!(!none.is_some());
881 assert_eq!(none.get(), None);
882
883 let some = OptionalEpochId::some(EpochId::new(42));
885 assert!(some.is_some());
886 assert!(!some.is_none());
887 assert_eq!(some.get(), Some(EpochId::new(42)));
888
889 let zero = OptionalEpochId::some(EpochId::new(0));
891 assert!(zero.is_some());
892 assert_eq!(zero.get(), Some(EpochId::new(0)));
893 }
894
895 #[test]
896 fn test_hot_version_ref_visibility() {
897 let hot = HotVersionRef::new(EpochId::new(5), 100, TxId::new(1));
898
899 assert!(!hot.is_visible_at(EpochId::new(4)));
901
902 assert!(hot.is_visible_at(EpochId::new(5)));
904 assert!(hot.is_visible_at(EpochId::new(10)));
905 }
906
907 #[test]
908 fn test_hot_version_ref_deleted_visibility() {
909 let mut hot = HotVersionRef::new(EpochId::new(5), 100, TxId::new(1));
910 hot.deleted_epoch = OptionalEpochId::some(EpochId::new(10));
911
912 assert!(hot.is_visible_at(EpochId::new(5)));
914 assert!(hot.is_visible_at(EpochId::new(9)));
915
916 assert!(!hot.is_visible_at(EpochId::new(10)));
918 assert!(!hot.is_visible_at(EpochId::new(15)));
919 }
920
921 #[test]
922 fn test_hot_version_ref_transaction_visibility() {
923 let hot = HotVersionRef::new(EpochId::new(5), 100, TxId::new(1));
924
925 assert!(hot.is_visible_to(EpochId::new(3), TxId::new(1)));
927
928 assert!(!hot.is_visible_to(EpochId::new(3), TxId::new(2)));
930 assert!(hot.is_visible_to(EpochId::new(5), TxId::new(2)));
931 }
932
933 #[test]
934 fn test_version_index_basic() {
935 let hot = HotVersionRef::new(EpochId::new(1), 0, TxId::new(1));
936 let mut index = VersionIndex::with_initial(hot);
937
938 assert!(index.visible_at(EpochId::new(1)).is_some());
940 assert!(index.visible_at(EpochId::new(0)).is_none());
941
942 let hot2 = HotVersionRef::new(EpochId::new(5), 100, TxId::new(2));
944 index.add_hot(hot2);
945
946 let v1 = index.visible_at(EpochId::new(4)).unwrap();
948 assert!(matches!(v1, VersionRef::Hot(h) if h.arena_offset == 0));
949
950 let v2 = index.visible_at(EpochId::new(5)).unwrap();
951 assert!(matches!(v2, VersionRef::Hot(h) if h.arena_offset == 100));
952 }
953
954 #[test]
955 fn test_version_index_deletion() {
956 let hot = HotVersionRef::new(EpochId::new(1), 0, TxId::new(1));
957 let mut index = VersionIndex::with_initial(hot);
958
959 assert!(index.mark_deleted(EpochId::new(5)));
961
962 assert!(index.visible_at(EpochId::new(4)).is_some());
964 assert!(index.visible_at(EpochId::new(5)).is_none());
965 assert!(index.visible_at(EpochId::new(10)).is_none());
966 }
967
968 #[test]
969 fn test_version_index_transaction_visibility() {
970 let tx = TxId::new(10);
971 let hot = HotVersionRef::new(EpochId::new(5), 0, tx);
972 let index = VersionIndex::with_initial(hot);
973
974 assert!(index.visible_to(EpochId::new(3), tx).is_some());
976
977 assert!(index.visible_to(EpochId::new(3), TxId::new(20)).is_none());
979 assert!(index.visible_to(EpochId::new(5), TxId::new(20)).is_some());
980 }
981
982 #[test]
983 fn test_version_index_rollback() {
984 let tx1 = TxId::new(10);
985 let tx2 = TxId::new(20);
986
987 let mut index = VersionIndex::new();
988 index.add_hot(HotVersionRef::new(EpochId::new(1), 0, tx1));
989 index.add_hot(HotVersionRef::new(EpochId::new(2), 100, tx2));
990 index.add_hot(HotVersionRef::new(EpochId::new(3), 200, tx2));
991
992 assert_eq!(index.version_count(), 3);
993 assert!(index.modified_by(tx1));
994 assert!(index.modified_by(tx2));
995
996 index.remove_versions_by(tx2);
998
999 assert_eq!(index.version_count(), 1);
1000 assert!(index.modified_by(tx1));
1001 assert!(!index.modified_by(tx2));
1002
1003 let v = index.visible_at(EpochId::new(10)).unwrap();
1005 assert!(matches!(v, VersionRef::Hot(h) if h.created_by == tx1));
1006 }
1007
1008 #[test]
1009 fn test_version_index_gc() {
1010 let mut index = VersionIndex::new();
1011
1012 for epoch in [1, 3, 5] {
1014 index.add_hot(HotVersionRef::new(
1015 EpochId::new(epoch),
1016 epoch as u32 * 100,
1017 TxId::new(epoch),
1018 ));
1019 }
1020
1021 assert_eq!(index.version_count(), 3);
1022
1023 index.gc(EpochId::new(4));
1026
1027 assert_eq!(index.version_count(), 2);
1028
1029 assert!(index.visible_at(EpochId::new(5)).is_some());
1031 assert!(index.visible_at(EpochId::new(3)).is_some());
1032 }
1033
1034 #[test]
1035 fn test_version_index_conflict_detection() {
1036 let tx1 = TxId::new(10);
1037 let tx2 = TxId::new(20);
1038
1039 let mut index = VersionIndex::new();
1040 index.add_hot(HotVersionRef::new(EpochId::new(1), 0, tx1));
1041 index.add_hot(HotVersionRef::new(EpochId::new(5), 100, tx2));
1042
1043 assert!(index.has_conflict(EpochId::new(0), tx1));
1045
1046 assert!(index.has_conflict(EpochId::new(0), tx2));
1048
1049 assert!(!index.has_conflict(EpochId::new(5), tx1));
1051
1052 assert!(!index.has_conflict(EpochId::new(1), tx2));
1054
1055 let mut index2 = VersionIndex::new();
1057 index2.add_hot(HotVersionRef::new(EpochId::new(5), 0, tx1));
1058 assert!(!index2.has_conflict(EpochId::new(0), tx1));
1059 }
1060
1061 #[test]
1062 fn test_version_index_smallvec_no_heap() {
1063 let mut index = VersionIndex::new();
1064
1065 for i in 0..2 {
1067 index.add_hot(HotVersionRef::new(EpochId::new(i), i as u32, TxId::new(i)));
1068 }
1069
1070 assert!(!index.hot_spilled());
1072 assert!(!index.cold_spilled());
1073 }
1074
1075 #[test]
1076 fn test_version_index_freeze_epoch() {
1077 let mut index = VersionIndex::new();
1078 index.add_hot(HotVersionRef::new(EpochId::new(1), 0, TxId::new(1)));
1079 index.add_hot(HotVersionRef::new(EpochId::new(2), 100, TxId::new(2)));
1080
1081 assert_eq!(index.hot_count(), 2);
1082 assert_eq!(index.cold_count(), 0);
1083
1084 let cold_ref = ColdVersionRef {
1086 epoch: EpochId::new(1),
1087 block_offset: 0,
1088 length: 32,
1089 created_by: TxId::new(1),
1090 deleted_epoch: OptionalEpochId::NONE,
1091 };
1092 index.freeze_epoch(EpochId::new(1), std::iter::once(cold_ref));
1093
1094 assert_eq!(index.hot_count(), 1);
1096 assert_eq!(index.cold_count(), 1);
1097
1098 assert!(index.visible_at(EpochId::new(1)).is_some());
1100 assert!(index.visible_at(EpochId::new(2)).is_some());
1101
1102 let v1 = index.visible_at(EpochId::new(1)).unwrap();
1104 assert!(v1.is_cold());
1105
1106 let v2 = index.visible_at(EpochId::new(2)).unwrap();
1107 assert!(v2.is_hot());
1108 }
1109
1110 #[test]
1111 fn test_version_ref_accessors() {
1112 let hot = HotVersionRef::new(EpochId::new(5), 100, TxId::new(10));
1113 let vr = VersionRef::Hot(hot);
1114
1115 assert_eq!(vr.epoch(), EpochId::new(5));
1116 assert_eq!(vr.created_by(), TxId::new(10));
1117 assert!(vr.is_hot());
1118 assert!(!vr.is_cold());
1119 }
1120
1121 #[test]
1122 fn test_version_index_latest_epoch() {
1123 let mut index = VersionIndex::new();
1124 assert_eq!(index.latest_epoch(), EpochId::INITIAL);
1125
1126 index.add_hot(HotVersionRef::new(EpochId::new(5), 0, TxId::new(1)));
1127 assert_eq!(index.latest_epoch(), EpochId::new(5));
1128
1129 index.add_hot(HotVersionRef::new(EpochId::new(10), 100, TxId::new(2)));
1130 assert_eq!(index.latest_epoch(), EpochId::new(10));
1131
1132 index.remove_versions_by(TxId::new(2));
1134 assert_eq!(index.latest_epoch(), EpochId::new(5));
1135 }
1136
1137 #[test]
1138 fn test_version_index_default() {
1139 let index = VersionIndex::default();
1140 assert!(index.is_empty());
1141 assert_eq!(index.version_count(), 0);
1142 }
1143
1144 #[test]
1145 fn test_version_index_latest() {
1146 let mut index = VersionIndex::new();
1147 assert!(index.latest().is_none());
1148
1149 index.add_hot(HotVersionRef::new(EpochId::new(1), 0, TxId::new(1)));
1150 let latest = index.latest().unwrap();
1151 assert!(matches!(latest, VersionRef::Hot(h) if h.epoch == EpochId::new(1)));
1152
1153 index.add_hot(HotVersionRef::new(EpochId::new(5), 100, TxId::new(2)));
1154 let latest = index.latest().unwrap();
1155 assert!(matches!(latest, VersionRef::Hot(h) if h.epoch == EpochId::new(5)));
1156 }
1157}