Skip to main content

reifydb_core/key/
version_epoch.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2026 ReifyDB
3
4use std::ops::Bound;
5
6use reifydb_codec::key::{
7	deserializer::KeyDeserializer,
8	encoded::{EncodedKey, EncodedKeyRange},
9	serializer::KeySerializer,
10};
11use reifydb_runtime::version_epoch::EpochSeconds;
12
13use super::{EncodableKey, KeyKind};
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub struct VersionEpochKey {
17	pub bucket: EpochSeconds,
18}
19
20impl EncodableKey for VersionEpochKey {
21	const KIND: KeyKind = KeyKind::VersionEpoch;
22
23	fn encode(&self) -> EncodedKey {
24		let mut serializer = KeySerializer::with_capacity(9);
25		serializer.extend_u8(Self::KIND as u8).extend_u64(self.bucket.seconds());
26		serializer.to_encoded_key()
27	}
28
29	fn decode(key: &EncodedKey) -> Option<Self> {
30		let mut de = KeyDeserializer::from_bytes(key.as_slice());
31
32		let kind: KeyKind = de.read_u8().ok()?.try_into().ok()?;
33		if kind != Self::KIND {
34			return None;
35		}
36
37		let bucket = EpochSeconds::new(de.read_u64().ok()?);
38
39		Some(Self {
40			bucket,
41		})
42	}
43}
44
45impl VersionEpochKey {
46	pub fn encoded(bucket: EpochSeconds) -> EncodedKey {
47		Self {
48			bucket,
49		}
50		.encode()
51	}
52
53	pub fn floor_scan(target: EpochSeconds) -> EncodedKeyRange {
54		EncodedKeyRange::new(
55			Bound::Included(Self::encoded(target)),
56			Bound::Included(Self::encoded(EpochSeconds::new(0))),
57		)
58	}
59
60	pub fn older_than(cutoff: EpochSeconds) -> EncodedKeyRange {
61		EncodedKeyRange::new(
62			Bound::Excluded(Self::encoded(cutoff)),
63			Bound::Included(Self::encoded(EpochSeconds::new(0))),
64		)
65	}
66}
67
68#[cfg(test)]
69mod tests {
70	use std::ops::Bound;
71
72	use super::{EncodableKey, EpochSeconds, VersionEpochKey};
73
74	fn sec(seconds: u64) -> EpochSeconds {
75		EpochSeconds::new(seconds)
76	}
77
78	#[test]
79	fn test_encode_decode() {
80		let key = VersionEpochKey {
81			bucket: sec(0x0123456789ABCDEF),
82		};
83		let encoded = key.encode();
84		let decoded = VersionEpochKey::decode(&encoded).unwrap();
85		assert_eq!(decoded.bucket, sec(0x0123456789ABCDEF));
86	}
87
88	#[test]
89	fn test_descending_order_so_newer_bucket_sorts_first() {
90		let older = VersionEpochKey::encoded(sec(100));
91		let newer = VersionEpochKey::encoded(sec(200));
92		assert!(
93			newer < older,
94			"a newer (larger) bucket must encode to smaller key bytes so floor_scan can take the first entry at-or-after the target"
95		);
96	}
97
98	#[test]
99	fn test_floor_scan_lower_bound_is_target_bucket() {
100		let target = sec(150);
101		let range = VersionEpochKey::floor_scan(target);
102		assert_eq!(range.start, Bound::Included(VersionEpochKey::encoded(target)));
103		assert_eq!(range.end, Bound::Included(VersionEpochKey::encoded(sec(0))));
104		// A bucket exactly at the target is included; a bucket newer than the target is excluded.
105		assert!(VersionEpochKey::encoded(target) >= VersionEpochKey::encoded(target));
106		assert!(VersionEpochKey::encoded(sec(151)) < VersionEpochKey::encoded(target));
107	}
108}