Skip to main content

reifydb_store_multi/store/
version.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2025 ReifyDB
3
4use reifydb_core::common::CommitVersion;
5use reifydb_type::{Result, util::cowvec::CowVec};
6
7use crate::tier::{EntryKind, TierStorage};
8
9/// Result of a versioned get operation
10#[derive(Debug, Clone)]
11pub enum VersionedGetResult {
12	/// Found a value at this version
13	Value {
14		value: CowVec<u8>,
15		version: CommitVersion,
16	},
17	/// Found a tombstone (deletion) at this version
18	Tombstone,
19	/// Key not found at or before the requested version
20	NotFound,
21}
22
23/// Get the latest version of a key at or before the given version.
24pub fn get_at_version<S: TierStorage>(
25	storage: &S,
26	table: EntryKind,
27	key: &[u8],
28	version: CommitVersion,
29) -> Result<VersionedGetResult> {
30	// The storage layer now handles version lookups directly
31	match storage.get(table, key, version)? {
32		Some(value) => Ok(VersionedGetResult::Value {
33			value,
34			version,
35		}),
36		None => {
37			// Need to determine if it's a tombstone or not found
38			// Get all versions and check if any version <= requested exists
39			let all_versions = storage.get_all_versions(table, key)?;
40
41			// Find the latest version <= requested
42			for (v, value) in all_versions {
43				if v <= version {
44					// Found a version at or before requested
45					return match value {
46						Some(val) => Ok(VersionedGetResult::Value {
47							value: val,
48							version: v,
49						}),
50						None => Ok(VersionedGetResult::Tombstone),
51					};
52				}
53			}
54
55			// No version exists at or before requested version
56			Ok(VersionedGetResult::NotFound)
57		}
58	}
59}
60
61#[cfg(test)]
62pub mod tests {
63	use std::collections::HashMap;
64
65	use super::*;
66	use crate::hot::{memory::storage::MemoryPrimitiveStorage, storage::HotStorage};
67
68	#[test]
69	fn test_get_at_version_basic() {
70		let storage = MemoryPrimitiveStorage::new();
71
72		let key = CowVec::new(b"test_key".to_vec());
73		let version = CommitVersion(42);
74
75		// Insert a value
76		storage.set(
77			version,
78			HashMap::from([(EntryKind::Multi, vec![(key.clone(), Some(CowVec::new(b"value".to_vec())))])]),
79		)
80		.unwrap();
81
82		// Get at exact version
83		match get_at_version(&storage, EntryKind::Multi, &key, version).unwrap() {
84			VersionedGetResult::Value {
85				value,
86				..
87			} => {
88				assert_eq!(value.as_slice(), b"value");
89			}
90			_ => panic!("Expected Value result"),
91		}
92
93		// Get at higher version should still work
94		match get_at_version(&storage, EntryKind::Multi, &key, CommitVersion(100)).unwrap() {
95			VersionedGetResult::Value {
96				value,
97				..
98			} => {
99				assert_eq!(value.as_slice(), b"value");
100			}
101			_ => panic!("Expected Value result"),
102		}
103	}
104
105	#[test]
106	fn test_get_at_version_not_found() {
107		let storage = MemoryPrimitiveStorage::new();
108
109		let result = get_at_version(&storage, EntryKind::Multi, b"nonexistent", CommitVersion(100)).unwrap();
110		assert!(matches!(result, VersionedGetResult::NotFound));
111	}
112
113	#[test]
114	fn test_get_at_version_tombstone() {
115		let storage = MemoryPrimitiveStorage::new();
116
117		let key = CowVec::new(b"test_key".to_vec());
118
119		// Insert a tombstone (None value)
120		storage.set(CommitVersion(1), HashMap::from([(EntryKind::Multi, vec![(key.clone(), None)])])).unwrap();
121
122		let result = get_at_version(&storage, EntryKind::Multi, &key, CommitVersion(1)).unwrap();
123		assert!(matches!(result, VersionedGetResult::Tombstone));
124	}
125
126	#[test]
127	fn test_get_at_version_multiple_versions() {
128		let storage = HotStorage::memory();
129
130		let key = CowVec::new(b"test_key".to_vec());
131
132		// Insert multiple versions
133		storage.set(
134			CommitVersion(1),
135			HashMap::from([(EntryKind::Multi, vec![(key.clone(), Some(CowVec::new(b"v1".to_vec())))])]),
136		)
137		.unwrap();
138		storage.set(
139			CommitVersion(5),
140			HashMap::from([(EntryKind::Multi, vec![(key.clone(), Some(CowVec::new(b"v5".to_vec())))])]),
141		)
142		.unwrap();
143		storage.set(
144			CommitVersion(10),
145			HashMap::from([(EntryKind::Multi, vec![(key.clone(), Some(CowVec::new(b"v10".to_vec())))])]),
146		)
147		.unwrap();
148
149		// Get at version 3 should return v1 (latest <= 3)
150		match get_at_version(&storage, EntryKind::Multi, &key, CommitVersion(3)).unwrap() {
151			VersionedGetResult::Value {
152				value,
153				..
154			} => {
155				assert_eq!(value.as_slice(), b"v1");
156			}
157			_ => panic!("Expected Value result"),
158		}
159
160		// Get at version 7 should return v5 (latest <= 7)
161		match get_at_version(&storage, EntryKind::Multi, &key, CommitVersion(7)).unwrap() {
162			VersionedGetResult::Value {
163				value,
164				..
165			} => {
166				assert_eq!(value.as_slice(), b"v5");
167			}
168			_ => panic!("Expected Value result"),
169		}
170
171		// Get at version 15 should return v10 (latest <= 15)
172		match get_at_version(&storage, EntryKind::Multi, &key, CommitVersion(15)).unwrap() {
173			VersionedGetResult::Value {
174				value,
175				..
176			} => {
177				assert_eq!(value.as_slice(), b"v10");
178			}
179			_ => panic!("Expected Value result"),
180		}
181	}
182
183	#[test]
184	fn test_get_at_version_before_any_version() {
185		let storage = HotStorage::memory();
186
187		let key = CowVec::new(b"test_key".to_vec());
188
189		// Insert at version 10
190		storage.set(
191			CommitVersion(10),
192			HashMap::from([(EntryKind::Multi, vec![(key.clone(), Some(CowVec::new(b"value".to_vec())))])]),
193		)
194		.unwrap();
195
196		// Get at version 5 (before any version exists) should return NotFound
197		let result = get_at_version(&storage, EntryKind::Multi, &key, CommitVersion(5)).unwrap();
198		assert!(matches!(result, VersionedGetResult::NotFound));
199	}
200}