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