1use std::collections::VecDeque;
9
10#[cfg(feature = "tiered-storage")]
11use smallvec::SmallVec;
12
13use crate::types::{EpochId, TransactionId};
14
15#[derive(Debug, Clone, Copy)]
17pub struct VersionInfo {
18 pub created_epoch: EpochId,
20 pub deleted_epoch: Option<EpochId>,
22 pub created_by: TransactionId,
24 pub deleted_by: Option<TransactionId>,
27}
28
29impl VersionInfo {
30 #[must_use]
32 pub fn new(created_epoch: EpochId, created_by: TransactionId) -> Self {
33 Self {
34 created_epoch,
35 deleted_epoch: None,
36 created_by,
37 deleted_by: None,
38 }
39 }
40
41 pub fn mark_deleted(&mut self, epoch: EpochId, deleted_by: TransactionId) {
43 self.deleted_epoch = Some(epoch);
44 self.deleted_by = Some(deleted_by);
45 }
46
47 pub fn unmark_deleted_by(&mut self, transaction_id: TransactionId) -> bool {
51 if self.deleted_by == Some(transaction_id) {
52 self.deleted_epoch = None;
53 self.deleted_by = None;
54 true
55 } else {
56 false
57 }
58 }
59
60 #[inline]
62 #[must_use]
63 pub fn is_visible_at(&self, epoch: EpochId) -> bool {
64 if !self.created_epoch.is_visible_at(epoch) {
67 return false;
68 }
69
70 if let Some(deleted) = self.deleted_epoch {
71 deleted.as_u64() > epoch.as_u64()
73 } else {
74 true
75 }
76 }
77
78 #[inline]
85 #[must_use]
86 pub fn is_visible_to(&self, viewing_epoch: EpochId, viewing_tx: TransactionId) -> bool {
87 if self.deleted_by == Some(viewing_tx) {
89 return false;
90 }
91
92 if self.created_by == viewing_tx {
94 return self.deleted_epoch.is_none();
95 }
96
97 self.is_visible_at(viewing_epoch)
99 }
100}
101
102#[derive(Debug, Clone)]
104pub struct Version<T> {
105 pub info: VersionInfo,
107 pub data: T,
109}
110
111impl<T> Version<T> {
112 #[must_use]
114 pub fn new(data: T, created_epoch: EpochId, created_by: TransactionId) -> Self {
115 Self {
116 info: VersionInfo::new(created_epoch, created_by),
117 data,
118 }
119 }
120}
121
122#[derive(Debug, Clone)]
128pub struct VersionChain<T> {
129 versions: VecDeque<Version<T>>,
131}
132
133impl<T> VersionChain<T> {
134 #[must_use]
136 pub fn new() -> Self {
137 Self {
138 versions: VecDeque::new(),
139 }
140 }
141
142 #[must_use]
144 pub fn with_initial(data: T, created_epoch: EpochId, created_by: TransactionId) -> Self {
145 let mut chain = Self::new();
146 chain.add_version(data, created_epoch, created_by);
147 chain
148 }
149
150 pub fn add_version(&mut self, data: T, created_epoch: EpochId, created_by: TransactionId) {
154 let version = Version::new(data, created_epoch, created_by);
155 self.versions.push_front(version);
156 }
157
158 #[inline]
163 #[must_use]
164 pub fn visible_at(&self, epoch: EpochId) -> Option<&T> {
165 self.versions
166 .iter()
167 .find(|v| v.info.is_visible_at(epoch))
168 .map(|v| &v.data)
169 }
170
171 #[inline]
175 #[must_use]
176 pub fn visible_to(&self, epoch: EpochId, tx: TransactionId) -> Option<&T> {
177 self.versions
178 .iter()
179 .find(|v| v.info.is_visible_to(epoch, tx))
180 .map(|v| &v.data)
181 }
182
183 pub fn mark_deleted(&mut self, delete_epoch: EpochId, deleted_by: TransactionId) -> bool {
187 for version in &mut self.versions {
188 if version.info.deleted_epoch.is_none() {
189 version.info.mark_deleted(delete_epoch, deleted_by);
190 return true;
191 }
192 }
193 false
194 }
195
196 pub fn unmark_deleted_by(&mut self, tx: TransactionId) -> bool {
201 let mut any_undeleted = false;
202 for version in &mut self.versions {
203 if version.info.unmark_deleted_by(tx) {
204 any_undeleted = true;
205 }
206 }
207 any_undeleted
208 }
209
210 #[must_use]
212 pub fn modified_by(&self, tx: TransactionId) -> bool {
213 self.versions.iter().any(|v| v.info.created_by == tx)
214 }
215
216 #[must_use]
218 pub fn deleted_by(&self, tx: TransactionId) -> bool {
219 self.versions.iter().any(|v| v.info.deleted_by == Some(tx))
220 }
221
222 pub fn remove_versions_by(&mut self, tx: TransactionId) {
226 self.versions.retain(|v| v.info.created_by != tx);
227 }
228
229 pub fn finalize_epochs(&mut self, transaction_id: TransactionId, commit_epoch: EpochId) {
234 for version in &mut self.versions {
235 if version.info.created_by == transaction_id
236 && version.info.created_epoch == EpochId::PENDING
237 {
238 version.info.created_epoch = commit_epoch;
239 }
240 }
241 }
242
243 #[must_use]
248 pub fn has_conflict(&self, start_epoch: EpochId, our_tx: TransactionId) -> bool {
249 self.versions.iter().any(|v| {
250 v.info.created_by != our_tx && v.info.created_epoch.as_u64() > start_epoch.as_u64()
251 })
252 }
253
254 #[must_use]
256 pub fn version_count(&self) -> usize {
257 self.versions.len()
258 }
259
260 #[must_use]
262 pub fn is_empty(&self) -> bool {
263 self.versions.is_empty()
264 }
265
266 pub fn gc(&mut self, min_epoch: EpochId) {
270 if self.versions.is_empty() {
271 return;
272 }
273
274 let mut keep_count = 0;
275 let mut found_old_visible = false;
276
277 for (i, version) in self.versions.iter().enumerate() {
278 if version.info.created_epoch.as_u64() >= min_epoch.as_u64() {
279 keep_count = i + 1;
280 } else if !found_old_visible {
281 found_old_visible = true;
283 keep_count = i + 1;
284 }
285 }
286
287 self.versions.truncate(keep_count);
288 }
289
290 pub fn history(&self) -> impl Iterator<Item = (&VersionInfo, &T)> {
295 self.versions.iter().map(|v| (&v.info, &v.data))
296 }
297
298 #[must_use]
300 pub fn latest(&self) -> Option<&T> {
301 self.versions.front().map(|v| &v.data)
302 }
303
304 #[must_use]
306 pub fn latest_mut(&mut self) -> Option<&mut T> {
307 self.versions.front_mut().map(|v| &mut v.data)
308 }
309
310 #[must_use]
315 pub fn heap_memory_bytes(&self) -> usize {
316 self.versions.capacity() * std::mem::size_of::<Version<T>>()
317 }
318}
319
320impl<T> Default for VersionChain<T> {
321 fn default() -> Self {
322 Self::new()
323 }
324}
325
326impl<T: Clone> VersionChain<T> {
327 pub fn get_mut(
332 &mut self,
333 epoch: EpochId,
334 tx: TransactionId,
335 modify_epoch: EpochId,
336 ) -> Option<&mut T> {
337 let visible_idx = self
339 .versions
340 .iter()
341 .position(|v| v.info.is_visible_to(epoch, tx))?;
342
343 let visible = &self.versions[visible_idx];
344
345 if visible.info.created_by == tx {
346 Some(&mut self.versions[visible_idx].data)
348 } else {
349 let new_data = visible.data.clone();
351 self.add_version(new_data, modify_epoch, tx);
352 Some(&mut self.versions[0].data)
353 }
354 }
355}
356
357#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
370#[repr(transparent)]
371#[cfg(feature = "tiered-storage")]
372pub struct OptionalEpochId(u32);
373
374#[cfg(feature = "tiered-storage")]
375impl OptionalEpochId {
376 pub const NONE: Self = Self(u32::MAX);
378
379 #[must_use]
384 pub fn some(epoch: EpochId) -> Self {
385 assert!(
386 epoch.as_u64() < u64::from(u32::MAX),
387 "epoch {} exceeds OptionalEpochId capacity (max {})",
388 epoch.as_u64(),
389 u32::MAX as u64 - 1
390 );
391 Self(epoch.as_u64() as u32)
392 }
393
394 #[inline]
396 #[must_use]
397 pub fn get(self) -> Option<EpochId> {
398 if self.0 == u32::MAX {
399 None
400 } else {
401 Some(EpochId::new(u64::from(self.0)))
402 }
403 }
404
405 #[must_use]
407 pub fn is_some(self) -> bool {
408 self.0 != u32::MAX
409 }
410
411 #[inline]
413 #[must_use]
414 pub fn is_none(self) -> bool {
415 self.0 == u32::MAX
416 }
417}
418
419#[derive(Debug, Clone, Copy, PartialEq, Eq)]
432#[cfg(feature = "tiered-storage")]
433pub struct HotVersionRef {
434 pub epoch: EpochId,
439 pub arena_epoch: EpochId,
443 pub arena_offset: u32,
445 pub created_by: TransactionId,
447 pub deleted_epoch: OptionalEpochId,
449 pub deleted_by: Option<TransactionId>,
451}
452
453#[cfg(feature = "tiered-storage")]
454impl HotVersionRef {
455 #[must_use]
460 pub fn new(
461 epoch: EpochId,
462 arena_epoch: EpochId,
463 arena_offset: u32,
464 created_by: TransactionId,
465 ) -> Self {
466 Self {
467 epoch,
468 arena_epoch,
469 arena_offset,
470 created_by,
471 deleted_epoch: OptionalEpochId::NONE,
472 deleted_by: None,
473 }
474 }
475
476 pub fn mark_deleted(&mut self, epoch: EpochId, deleted_by: TransactionId) {
478 self.deleted_epoch = OptionalEpochId::some(epoch);
479 self.deleted_by = Some(deleted_by);
480 }
481
482 pub fn unmark_deleted_by(&mut self, transaction_id: TransactionId) -> bool {
484 if self.deleted_by == Some(transaction_id) {
485 self.deleted_epoch = OptionalEpochId::NONE;
486 self.deleted_by = None;
487 true
488 } else {
489 false
490 }
491 }
492
493 #[inline]
495 #[must_use]
496 pub fn is_visible_at(&self, viewing_epoch: EpochId) -> bool {
497 if !self.epoch.is_visible_at(viewing_epoch) {
499 return false;
500 }
501 match self.deleted_epoch.get() {
503 Some(deleted) => deleted.as_u64() > viewing_epoch.as_u64(),
504 None => true,
505 }
506 }
507
508 #[inline]
510 #[must_use]
511 pub fn is_visible_to(&self, viewing_epoch: EpochId, viewing_tx: TransactionId) -> bool {
512 if self.deleted_by == Some(viewing_tx) {
514 return false;
515 }
516 if self.created_by == viewing_tx {
518 return self.deleted_epoch.is_none();
519 }
520 self.is_visible_at(viewing_epoch)
522 }
523}
524
525#[derive(Debug, Clone, Copy, PartialEq, Eq)]
538#[cfg(feature = "tiered-storage")]
539pub struct ColdVersionRef {
540 pub epoch: EpochId,
542 pub block_offset: u32,
544 pub length: u16,
546 pub created_by: TransactionId,
548 pub deleted_epoch: OptionalEpochId,
550 pub deleted_by: Option<TransactionId>,
552}
553
554#[cfg(feature = "tiered-storage")]
555impl ColdVersionRef {
556 #[inline]
558 #[must_use]
559 pub fn is_visible_at(&self, viewing_epoch: EpochId) -> bool {
560 if !self.epoch.is_visible_at(viewing_epoch) {
561 return false;
562 }
563 match self.deleted_epoch.get() {
564 Some(deleted) => deleted.as_u64() > viewing_epoch.as_u64(),
565 None => true,
566 }
567 }
568
569 #[inline]
571 #[must_use]
572 pub fn is_visible_to(&self, viewing_epoch: EpochId, viewing_tx: TransactionId) -> bool {
573 if self.deleted_by == Some(viewing_tx) {
575 return false;
576 }
577 if self.created_by == viewing_tx {
578 return self.deleted_epoch.is_none();
579 }
580 self.is_visible_at(viewing_epoch)
581 }
582}
583
584#[derive(Debug, Clone, Copy)]
586#[cfg(feature = "tiered-storage")]
587pub enum VersionRef {
588 Hot(HotVersionRef),
590 Cold(ColdVersionRef),
592}
593
594#[cfg(feature = "tiered-storage")]
595impl VersionRef {
596 #[must_use]
598 pub fn epoch(&self) -> EpochId {
599 match self {
600 Self::Hot(h) => h.epoch,
601 Self::Cold(c) => c.epoch,
602 }
603 }
604
605 #[must_use]
607 pub fn created_by(&self) -> TransactionId {
608 match self {
609 Self::Hot(h) => h.created_by,
610 Self::Cold(c) => c.created_by,
611 }
612 }
613
614 #[must_use]
616 pub fn is_hot(&self) -> bool {
617 matches!(self, Self::Hot(_))
618 }
619
620 #[must_use]
622 pub fn is_cold(&self) -> bool {
623 matches!(self, Self::Cold(_))
624 }
625
626 #[must_use]
628 pub fn deleted_epoch(&self) -> Option<EpochId> {
629 match self {
630 Self::Hot(h) => h.deleted_epoch.get(),
631 Self::Cold(c) => c.deleted_epoch.get(),
632 }
633 }
634}
635
636#[derive(Debug, Clone)]
652#[cfg(feature = "tiered-storage")]
653pub struct VersionIndex {
654 hot: SmallVec<[HotVersionRef; 2]>,
656 cold: SmallVec<[ColdVersionRef; 4]>,
658 latest_epoch: EpochId,
660}
661
662#[cfg(feature = "tiered-storage")]
663impl VersionIndex {
664 #[must_use]
666 pub fn new() -> Self {
667 Self {
668 hot: SmallVec::new(),
669 cold: SmallVec::new(),
670 latest_epoch: EpochId::INITIAL,
671 }
672 }
673
674 #[must_use]
676 pub fn with_initial(hot_ref: HotVersionRef) -> Self {
677 let mut index = Self::new();
678 index.add_hot(hot_ref);
679 index
680 }
681
682 pub fn add_hot(&mut self, hot_ref: HotVersionRef) {
684 self.hot.insert(0, hot_ref);
686 self.latest_epoch = hot_ref.epoch;
687 }
688
689 #[must_use]
691 pub fn latest_epoch(&self) -> EpochId {
692 self.latest_epoch
693 }
694
695 #[must_use]
697 pub fn is_empty(&self) -> bool {
698 self.hot.is_empty() && self.cold.is_empty()
699 }
700
701 #[must_use]
703 pub fn version_count(&self) -> usize {
704 self.hot.len() + self.cold.len()
705 }
706
707 #[must_use]
709 pub fn hot_count(&self) -> usize {
710 self.hot.len()
711 }
712
713 #[must_use]
715 pub fn cold_count(&self) -> usize {
716 self.cold.len()
717 }
718
719 #[inline]
721 #[must_use]
722 pub fn visible_at(&self, epoch: EpochId) -> Option<VersionRef> {
723 for v in &self.hot {
725 if v.is_visible_at(epoch) {
726 return Some(VersionRef::Hot(*v));
727 }
728 }
729 for v in &self.cold {
731 if v.is_visible_at(epoch) {
732 return Some(VersionRef::Cold(*v));
733 }
734 }
735 None
736 }
737
738 #[inline]
740 #[must_use]
741 pub fn visible_to(&self, epoch: EpochId, tx: TransactionId) -> Option<VersionRef> {
742 for v in &self.hot {
744 if v.is_visible_to(epoch, tx) {
745 return Some(VersionRef::Hot(*v));
746 }
747 }
748 for v in &self.cold {
750 if v.is_visible_to(epoch, tx) {
751 return Some(VersionRef::Cold(*v));
752 }
753 }
754 None
755 }
756
757 pub fn mark_deleted(&mut self, delete_epoch: EpochId, deleted_by: TransactionId) -> bool {
761 for v in &mut self.hot {
763 if v.deleted_epoch.is_none() {
764 v.mark_deleted(delete_epoch, deleted_by);
765 return true;
766 }
767 }
768 for v in &mut self.cold {
770 if v.deleted_epoch.is_none() {
771 v.deleted_epoch = OptionalEpochId::some(delete_epoch);
772 v.deleted_by = Some(deleted_by);
773 return true;
774 }
775 }
776 false
777 }
778
779 pub fn unmark_deleted_by(&mut self, tx: TransactionId) -> bool {
783 let mut any_undeleted = false;
784 for v in &mut self.hot {
785 if v.unmark_deleted_by(tx) {
786 any_undeleted = true;
787 }
788 }
789 for v in &mut self.cold {
790 if v.deleted_by == Some(tx) {
791 v.deleted_epoch = OptionalEpochId::NONE;
792 v.deleted_by = None;
793 any_undeleted = true;
794 }
795 }
796 any_undeleted
797 }
798
799 #[must_use]
801 pub fn modified_by(&self, tx: TransactionId) -> bool {
802 self.hot.iter().any(|v| v.created_by == tx) || self.cold.iter().any(|v| v.created_by == tx)
803 }
804
805 #[must_use]
807 pub fn deleted_by(&self, tx: TransactionId) -> bool {
808 self.hot.iter().any(|v| v.deleted_by == Some(tx))
809 || self.cold.iter().any(|v| v.deleted_by == Some(tx))
810 }
811
812 pub fn remove_versions_by(&mut self, tx: TransactionId) {
814 self.hot.retain(|v| v.created_by != tx);
815 self.cold.retain(|v| v.created_by != tx);
816 self.recalculate_latest_epoch();
817 }
818
819 pub fn finalize_epochs(&mut self, transaction_id: TransactionId, commit_epoch: EpochId) {
824 for v in &mut self.hot {
825 if v.created_by == transaction_id && v.epoch == EpochId::PENDING {
826 v.epoch = commit_epoch;
827 }
828 }
829 self.recalculate_latest_epoch();
830 }
831
832 #[must_use]
837 pub fn has_conflict(&self, start_epoch: EpochId, our_tx: TransactionId) -> bool {
838 self.hot
839 .iter()
840 .any(|v| v.created_by != our_tx && v.epoch.as_u64() > start_epoch.as_u64())
841 || self
842 .cold
843 .iter()
844 .any(|v| v.created_by != our_tx && v.epoch.as_u64() > start_epoch.as_u64())
845 }
846
847 pub fn gc(&mut self, min_epoch: EpochId) {
851 if self.is_empty() {
852 return;
853 }
854
855 let mut found_old_visible = false;
859
860 self.hot.retain(|v| {
861 if v.epoch.as_u64() >= min_epoch.as_u64() {
862 true
863 } else if !found_old_visible {
864 found_old_visible = true;
865 true
866 } else {
867 false
868 }
869 });
870
871 if !found_old_visible {
873 self.cold.retain(|v| {
874 if v.epoch.as_u64() >= min_epoch.as_u64() {
875 true
876 } else if !found_old_visible {
877 found_old_visible = true;
878 true
879 } else {
880 false
881 }
882 });
883 } else {
884 self.cold.retain(|v| v.epoch.as_u64() >= min_epoch.as_u64());
886 }
887 }
888
889 #[must_use]
893 pub fn version_epochs(&self) -> Vec<EpochId> {
894 let mut epochs: Vec<EpochId> = self
895 .hot
896 .iter()
897 .map(|v| v.epoch)
898 .chain(self.cold.iter().map(|v| v.epoch))
899 .collect();
900 epochs.sort_by_key(|e| std::cmp::Reverse(e.as_u64()));
901 epochs
902 }
903
904 #[must_use]
909 pub fn version_history(&self) -> Vec<(EpochId, Option<EpochId>, VersionRef)> {
910 let mut versions: Vec<(EpochId, Option<EpochId>, VersionRef)> = self
911 .hot
912 .iter()
913 .map(|v| (v.epoch, v.deleted_epoch.get(), VersionRef::Hot(*v)))
914 .chain(
915 self.cold
916 .iter()
917 .map(|v| (v.epoch, v.deleted_epoch.get(), VersionRef::Cold(*v))),
918 )
919 .collect();
920 versions.sort_by_key(|v| std::cmp::Reverse(v.0.as_u64()));
921 versions
922 }
923
924 #[must_use]
926 pub fn latest(&self) -> Option<VersionRef> {
927 self.hot
928 .first()
929 .map(|v| VersionRef::Hot(*v))
930 .or_else(|| self.cold.first().map(|v| VersionRef::Cold(*v)))
931 }
932
933 pub fn freeze_epoch(
938 &mut self,
939 epoch: EpochId,
940 cold_refs: impl Iterator<Item = ColdVersionRef>,
941 ) {
942 self.hot.retain(|v| v.epoch != epoch);
944
945 self.cold.extend(cold_refs);
947
948 self.cold
950 .sort_by(|a, b| b.epoch.as_u64().cmp(&a.epoch.as_u64()));
951
952 self.recalculate_latest_epoch();
953 }
954
955 pub fn hot_refs_for_epoch(&self, epoch: EpochId) -> impl Iterator<Item = &HotVersionRef> {
957 self.hot.iter().filter(move |v| v.epoch == epoch)
958 }
959
960 #[must_use]
962 pub fn hot_spilled(&self) -> bool {
963 self.hot.spilled()
964 }
965
966 #[must_use]
968 pub fn cold_spilled(&self) -> bool {
969 self.cold.spilled()
970 }
971
972 fn recalculate_latest_epoch(&mut self) {
973 self.latest_epoch = self
974 .hot
975 .first()
976 .map(|v| v.epoch)
977 .or_else(|| self.cold.first().map(|v| v.epoch))
978 .unwrap_or(EpochId::INITIAL);
979 }
980}
981
982#[cfg(feature = "tiered-storage")]
983impl Default for VersionIndex {
984 fn default() -> Self {
985 Self::new()
986 }
987}
988
989#[cfg(test)]
990mod tests {
991 use super::*;
992
993 #[test]
994 fn test_version_visibility() {
995 let v = VersionInfo::new(EpochId::new(5), TransactionId::new(1));
996
997 assert!(!v.is_visible_at(EpochId::new(4)));
999
1000 assert!(v.is_visible_at(EpochId::new(5)));
1002 assert!(v.is_visible_at(EpochId::new(10)));
1003 }
1004
1005 #[test]
1006 fn test_deleted_version_visibility() {
1007 let mut v = VersionInfo::new(EpochId::new(5), TransactionId::new(1));
1008 v.mark_deleted(EpochId::new(10), TransactionId::new(99));
1009
1010 assert!(v.is_visible_at(EpochId::new(5)));
1012 assert!(v.is_visible_at(EpochId::new(9)));
1013
1014 assert!(!v.is_visible_at(EpochId::new(10)));
1016 assert!(!v.is_visible_at(EpochId::new(15)));
1017 }
1018
1019 #[test]
1020 fn test_version_visibility_to_transaction() {
1021 let v = VersionInfo::new(EpochId::new(5), TransactionId::new(1));
1022
1023 assert!(v.is_visible_to(EpochId::new(3), TransactionId::new(1)));
1025
1026 assert!(!v.is_visible_to(EpochId::new(3), TransactionId::new(2)));
1028 assert!(v.is_visible_to(EpochId::new(5), TransactionId::new(2)));
1029 }
1030
1031 #[test]
1032 fn test_version_chain_basic() {
1033 let mut chain = VersionChain::with_initial("v1", EpochId::new(1), TransactionId::new(1));
1034
1035 assert_eq!(chain.visible_at(EpochId::new(1)), Some(&"v1"));
1037 assert_eq!(chain.visible_at(EpochId::new(0)), None);
1038
1039 chain.add_version("v2", EpochId::new(5), TransactionId::new(2));
1041
1042 assert_eq!(chain.visible_at(EpochId::new(1)), Some(&"v1"));
1044 assert_eq!(chain.visible_at(EpochId::new(4)), Some(&"v1"));
1045 assert_eq!(chain.visible_at(EpochId::new(5)), Some(&"v2"));
1046 assert_eq!(chain.visible_at(EpochId::new(10)), Some(&"v2"));
1047 }
1048
1049 #[test]
1050 fn test_version_chain_rollback() {
1051 let mut chain = VersionChain::with_initial("v1", EpochId::new(1), TransactionId::new(1));
1052 chain.add_version("v2", EpochId::new(5), TransactionId::new(2));
1053 chain.add_version("v3", EpochId::new(6), TransactionId::new(2));
1054
1055 assert_eq!(chain.version_count(), 3);
1056
1057 chain.remove_versions_by(TransactionId::new(2));
1059
1060 assert_eq!(chain.version_count(), 1);
1061 assert_eq!(chain.visible_at(EpochId::new(10)), Some(&"v1"));
1062 }
1063
1064 #[test]
1065 fn test_version_chain_deletion() {
1066 let mut chain = VersionChain::with_initial("v1", EpochId::new(1), TransactionId::new(1));
1067
1068 assert!(chain.mark_deleted(EpochId::new(5), TransactionId::new(99)));
1070
1071 assert_eq!(chain.visible_at(EpochId::new(4)), Some(&"v1"));
1073 assert_eq!(chain.visible_at(EpochId::new(5)), None);
1074 assert_eq!(chain.visible_at(EpochId::new(10)), None);
1075 }
1076}
1077
1078#[cfg(all(test, feature = "tiered-storage"))]
1083mod tiered_storage_tests {
1084 use super::*;
1085
1086 #[test]
1087 fn test_optional_epoch_id() {
1088 let none = OptionalEpochId::NONE;
1090 assert!(none.is_none());
1091 assert!(!none.is_some());
1092 assert_eq!(none.get(), None);
1093
1094 let some = OptionalEpochId::some(EpochId::new(42));
1096 assert!(some.is_some());
1097 assert!(!some.is_none());
1098 assert_eq!(some.get(), Some(EpochId::new(42)));
1099
1100 let zero = OptionalEpochId::some(EpochId::new(0));
1102 assert!(zero.is_some());
1103 assert_eq!(zero.get(), Some(EpochId::new(0)));
1104 }
1105
1106 #[test]
1107 fn test_hot_version_ref_visibility() {
1108 let hot = HotVersionRef::new(EpochId::new(5), EpochId::new(5), 100, TransactionId::new(1));
1109
1110 assert!(!hot.is_visible_at(EpochId::new(4)));
1112
1113 assert!(hot.is_visible_at(EpochId::new(5)));
1115 assert!(hot.is_visible_at(EpochId::new(10)));
1116 }
1117
1118 #[test]
1119 fn test_hot_version_ref_deleted_visibility() {
1120 let mut hot =
1121 HotVersionRef::new(EpochId::new(5), EpochId::new(5), 100, TransactionId::new(1));
1122 hot.deleted_epoch = OptionalEpochId::some(EpochId::new(10));
1123
1124 assert!(hot.is_visible_at(EpochId::new(5)));
1126 assert!(hot.is_visible_at(EpochId::new(9)));
1127
1128 assert!(!hot.is_visible_at(EpochId::new(10)));
1130 assert!(!hot.is_visible_at(EpochId::new(15)));
1131 }
1132
1133 #[test]
1134 fn test_hot_version_ref_transaction_visibility() {
1135 let hot = HotVersionRef::new(EpochId::new(5), EpochId::new(5), 100, TransactionId::new(1));
1136
1137 assert!(hot.is_visible_to(EpochId::new(3), TransactionId::new(1)));
1139
1140 assert!(!hot.is_visible_to(EpochId::new(3), TransactionId::new(2)));
1142 assert!(hot.is_visible_to(EpochId::new(5), TransactionId::new(2)));
1143 }
1144
1145 #[test]
1146 fn test_version_index_basic() {
1147 let hot = HotVersionRef::new(EpochId::new(1), EpochId::new(1), 0, TransactionId::new(1));
1148 let mut index = VersionIndex::with_initial(hot);
1149
1150 assert!(index.visible_at(EpochId::new(1)).is_some());
1152 assert!(index.visible_at(EpochId::new(0)).is_none());
1153
1154 let hot2 = HotVersionRef::new(EpochId::new(5), EpochId::new(5), 100, TransactionId::new(2));
1156 index.add_hot(hot2);
1157
1158 let v1 = index.visible_at(EpochId::new(4)).unwrap();
1160 assert!(matches!(v1, VersionRef::Hot(h) if h.arena_offset == 0));
1161
1162 let v2 = index.visible_at(EpochId::new(5)).unwrap();
1163 assert!(matches!(v2, VersionRef::Hot(h) if h.arena_offset == 100));
1164 }
1165
1166 #[test]
1167 fn test_version_index_deletion() {
1168 let hot = HotVersionRef::new(EpochId::new(1), EpochId::new(1), 0, TransactionId::new(1));
1169 let mut index = VersionIndex::with_initial(hot);
1170
1171 assert!(index.mark_deleted(EpochId::new(5), TransactionId::new(99)));
1173
1174 assert!(index.visible_at(EpochId::new(4)).is_some());
1176 assert!(index.visible_at(EpochId::new(5)).is_none());
1177 assert!(index.visible_at(EpochId::new(10)).is_none());
1178 }
1179
1180 #[test]
1181 fn test_version_index_transaction_visibility() {
1182 let tx = TransactionId::new(10);
1183 let hot = HotVersionRef::new(EpochId::new(5), EpochId::new(5), 0, tx);
1184 let index = VersionIndex::with_initial(hot);
1185
1186 assert!(index.visible_to(EpochId::new(3), tx).is_some());
1188
1189 assert!(
1191 index
1192 .visible_to(EpochId::new(3), TransactionId::new(20))
1193 .is_none()
1194 );
1195 assert!(
1196 index
1197 .visible_to(EpochId::new(5), TransactionId::new(20))
1198 .is_some()
1199 );
1200 }
1201
1202 #[test]
1203 fn test_version_index_rollback() {
1204 let tx1 = TransactionId::new(10);
1205 let tx2 = TransactionId::new(20);
1206
1207 let mut index = VersionIndex::new();
1208 index.add_hot(HotVersionRef::new(EpochId::new(1), EpochId::new(1), 0, tx1));
1209 index.add_hot(HotVersionRef::new(
1210 EpochId::new(2),
1211 EpochId::new(2),
1212 100,
1213 tx2,
1214 ));
1215 index.add_hot(HotVersionRef::new(
1216 EpochId::new(3),
1217 EpochId::new(3),
1218 200,
1219 tx2,
1220 ));
1221
1222 assert_eq!(index.version_count(), 3);
1223 assert!(index.modified_by(tx1));
1224 assert!(index.modified_by(tx2));
1225
1226 index.remove_versions_by(tx2);
1228
1229 assert_eq!(index.version_count(), 1);
1230 assert!(index.modified_by(tx1));
1231 assert!(!index.modified_by(tx2));
1232
1233 let v = index.visible_at(EpochId::new(10)).unwrap();
1235 assert!(matches!(v, VersionRef::Hot(h) if h.created_by == tx1));
1236 }
1237
1238 #[test]
1239 fn test_version_index_gc() {
1240 let mut index = VersionIndex::new();
1241
1242 for epoch in [1, 3, 5] {
1244 index.add_hot(HotVersionRef::new(
1245 EpochId::new(epoch),
1246 EpochId::new(epoch),
1247 epoch as u32 * 100,
1248 TransactionId::new(epoch),
1249 ));
1250 }
1251
1252 assert_eq!(index.version_count(), 3);
1253
1254 index.gc(EpochId::new(4));
1257
1258 assert_eq!(index.version_count(), 2);
1259
1260 assert!(index.visible_at(EpochId::new(5)).is_some());
1262 assert!(index.visible_at(EpochId::new(3)).is_some());
1263 }
1264
1265 #[test]
1266 fn test_version_index_conflict_detection() {
1267 let tx1 = TransactionId::new(10);
1268 let tx2 = TransactionId::new(20);
1269
1270 let mut index = VersionIndex::new();
1271 index.add_hot(HotVersionRef::new(EpochId::new(1), EpochId::new(1), 0, tx1));
1272 index.add_hot(HotVersionRef::new(
1273 EpochId::new(5),
1274 EpochId::new(5),
1275 100,
1276 tx2,
1277 ));
1278
1279 assert!(index.has_conflict(EpochId::new(0), tx1));
1281
1282 assert!(index.has_conflict(EpochId::new(0), tx2));
1284
1285 assert!(!index.has_conflict(EpochId::new(5), tx1));
1287
1288 assert!(!index.has_conflict(EpochId::new(1), tx2));
1290
1291 let mut index2 = VersionIndex::new();
1293 index2.add_hot(HotVersionRef::new(EpochId::new(5), EpochId::new(5), 0, tx1));
1294 assert!(!index2.has_conflict(EpochId::new(0), tx1));
1295 }
1296
1297 #[test]
1298 fn test_version_index_smallvec_no_heap() {
1299 let mut index = VersionIndex::new();
1300
1301 for i in 0..2 {
1303 index.add_hot(HotVersionRef::new(
1304 EpochId::new(i),
1305 EpochId::new(i),
1306 i as u32,
1307 TransactionId::new(i),
1308 ));
1309 }
1310
1311 assert!(!index.hot_spilled());
1313 assert!(!index.cold_spilled());
1314 }
1315
1316 #[test]
1317 fn test_version_index_freeze_epoch() {
1318 let mut index = VersionIndex::new();
1319 index.add_hot(HotVersionRef::new(
1320 EpochId::new(1),
1321 EpochId::new(1),
1322 0,
1323 TransactionId::new(1),
1324 ));
1325 index.add_hot(HotVersionRef::new(
1326 EpochId::new(2),
1327 EpochId::new(2),
1328 100,
1329 TransactionId::new(2),
1330 ));
1331
1332 assert_eq!(index.hot_count(), 2);
1333 assert_eq!(index.cold_count(), 0);
1334
1335 let cold_ref = ColdVersionRef {
1337 epoch: EpochId::new(1),
1338 block_offset: 0,
1339 length: 32,
1340 created_by: TransactionId::new(1),
1341 deleted_epoch: OptionalEpochId::NONE,
1342 deleted_by: None,
1343 };
1344 index.freeze_epoch(EpochId::new(1), std::iter::once(cold_ref));
1345
1346 assert_eq!(index.hot_count(), 1);
1348 assert_eq!(index.cold_count(), 1);
1349
1350 assert!(index.visible_at(EpochId::new(1)).is_some());
1352 assert!(index.visible_at(EpochId::new(2)).is_some());
1353
1354 let v1 = index.visible_at(EpochId::new(1)).unwrap();
1356 assert!(v1.is_cold());
1357
1358 let v2 = index.visible_at(EpochId::new(2)).unwrap();
1359 assert!(v2.is_hot());
1360 }
1361
1362 #[test]
1363 fn test_version_ref_accessors() {
1364 let hot = HotVersionRef::new(
1365 EpochId::new(5),
1366 EpochId::new(5),
1367 100,
1368 TransactionId::new(10),
1369 );
1370 let vr = VersionRef::Hot(hot);
1371
1372 assert_eq!(vr.epoch(), EpochId::new(5));
1373 assert_eq!(vr.created_by(), TransactionId::new(10));
1374 assert!(vr.is_hot());
1375 assert!(!vr.is_cold());
1376 }
1377
1378 #[test]
1379 fn test_version_index_latest_epoch() {
1380 let mut index = VersionIndex::new();
1381 assert_eq!(index.latest_epoch(), EpochId::INITIAL);
1382
1383 index.add_hot(HotVersionRef::new(
1384 EpochId::new(5),
1385 EpochId::new(5),
1386 0,
1387 TransactionId::new(1),
1388 ));
1389 assert_eq!(index.latest_epoch(), EpochId::new(5));
1390
1391 index.add_hot(HotVersionRef::new(
1392 EpochId::new(10),
1393 EpochId::new(10),
1394 100,
1395 TransactionId::new(2),
1396 ));
1397 assert_eq!(index.latest_epoch(), EpochId::new(10));
1398
1399 index.remove_versions_by(TransactionId::new(2));
1401 assert_eq!(index.latest_epoch(), EpochId::new(5));
1402 }
1403
1404 #[test]
1405 fn test_version_index_default() {
1406 let index = VersionIndex::default();
1407 assert!(index.is_empty());
1408 assert_eq!(index.version_count(), 0);
1409 }
1410
1411 #[test]
1412 fn test_version_index_latest() {
1413 let mut index = VersionIndex::new();
1414 assert!(index.latest().is_none());
1415
1416 index.add_hot(HotVersionRef::new(
1417 EpochId::new(1),
1418 EpochId::new(1),
1419 0,
1420 TransactionId::new(1),
1421 ));
1422 let latest = index.latest().unwrap();
1423 assert!(matches!(latest, VersionRef::Hot(h) if h.epoch == EpochId::new(1)));
1424
1425 index.add_hot(HotVersionRef::new(
1426 EpochId::new(5),
1427 EpochId::new(5),
1428 100,
1429 TransactionId::new(2),
1430 ));
1431 let latest = index.latest().unwrap();
1432 assert!(matches!(latest, VersionRef::Hot(h) if h.epoch == EpochId::new(5)));
1433 }
1434}