Skip to main content

reifydb_core/util/
multi.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2026 ReifyDB
3
4use std::{fmt::Debug, sync::Arc};
5
6use crossbeam_skiplist::SkipMap;
7use reifydb_runtime::sync::rwlock::RwLock;
8
9use crate::common::CommitVersion;
10
11#[derive(Debug)]
12pub struct MultiVersionContainer<T: Debug + Clone + Send + Sync + 'static> {
13	inner: Arc<RwLock<MultiVersionDefInner<T>>>,
14}
15
16#[derive(Debug)]
17struct MultiVersionDefInner<T: Debug + Clone + Send + Sync + 'static> {
18	versions: SkipMap<CommitVersion, Option<T>>,
19}
20
21impl<T: Debug + Clone + Send + Sync + 'static> MultiVersionContainer<T> {
22	pub fn new() -> Self {
23		Self {
24			inner: Arc::new(RwLock::new(MultiVersionDefInner {
25				versions: SkipMap::new(),
26			})),
27		}
28	}
29
30	pub fn insert(&self, version: impl Into<CommitVersion>, value: T) -> Option<Option<T>> {
31		let version = version.into();
32		let inner = self.inner.write();
33		if let Some(entry) = inner.versions.get(&version) {
34			let old_value = entry.value().clone();
35			inner.versions.insert(version, Some(value));
36			Some(old_value)
37		} else {
38			inner.versions.insert(version, Some(value));
39			None
40		}
41	}
42
43	pub fn get(&self, version: impl Into<CommitVersion>) -> Option<T> {
44		let version = version.into();
45		let inner = self.inner.read();
46
47		inner.versions.range(..=version).next_back().and_then(|entry| entry.value().clone())
48	}
49
50	pub fn get_latest(&self) -> Option<T> {
51		let inner = self.inner.read();
52		inner.versions.back().and_then(|entry| entry.value().clone())
53	}
54
55	pub fn versions(&self) -> Vec<CommitVersion> {
56		let inner = self.inner.read();
57		inner.versions.iter().map(|entry| *entry.key()).collect()
58	}
59
60	pub fn remove(&self, version: impl Into<CommitVersion>) -> Option<Option<T>> {
61		let version = version.into();
62		let inner = self.inner.write();
63
64		if let Some(entry) = inner.versions.get(&version) {
65			let old_value = entry.value().clone();
66			inner.versions.insert(version, None);
67			Some(old_value)
68		} else {
69			inner.versions.insert(version, None);
70			None
71		}
72	}
73
74	pub fn len(&self) -> usize {
75		let inner = self.inner.read();
76		inner.versions.len()
77	}
78
79	pub fn is_empty(&self) -> bool {
80		let inner = self.inner.read();
81		inner.versions.is_empty()
82	}
83
84	pub fn clear(&self) {
85		let inner = self.inner.write();
86		inner.versions.clear();
87	}
88}
89
90impl<T: Debug + Clone + Send + Sync + 'static> Clone for MultiVersionContainer<T> {
91	fn clone(&self) -> Self {
92		Self {
93			inner: Arc::clone(&self.inner),
94		}
95	}
96}
97
98impl<T: Debug + Clone + Send + Sync + 'static> Default for MultiVersionContainer<T> {
99	fn default() -> Self {
100		Self::new()
101	}
102}
103
104#[cfg(test)]
105pub mod tests {
106	use super::*;
107
108	#[derive(Debug, Clone, PartialEq)]
109	struct Test {
110		name: String,
111	}
112
113	#[test]
114	fn test_basic_operations() {
115		let multi = MultiVersionContainer::<Test>::new();
116
117		assert!(multi.is_empty());
118		assert_eq!(multi.len(), 0);
119		assert!(multi.get_latest().is_none());
120
121		let def1 = Test {
122			name: "v1".to_string(),
123		};
124		multi.insert(1, def1.clone());
125		assert!(!multi.is_empty());
126		assert_eq!(multi.len(), 1);
127
128		// A read above the newest version resolves to that version, not to none.
129		assert_eq!(multi.get(1), Some(def1.clone()));
130		assert_eq!(multi.get(2), Some(def1.clone()));
131		assert_eq!(multi.get_latest(), Some(def1.clone()));
132
133		let def2 = Test {
134			name: "v2".to_string(),
135		};
136		multi.insert(5, def2.clone());
137		assert_eq!(multi.len(), 2);
138		assert_eq!(multi.get(1), Some(def1.clone()));
139		assert_eq!(multi.get(3), Some(def1.clone()));
140		assert_eq!(multi.get(5), Some(def2.clone()));
141		assert_eq!(multi.get(10), Some(def2.clone()));
142		assert_eq!(multi.get_latest(), Some(def2.clone()));
143
144		multi.remove(7);
145		assert_eq!(multi.get(7), None);
146		assert_eq!(multi.get(10), None);
147
148		assert_eq!(multi.get_latest(), None);
149	}
150}