Skip to main content

reifydb_core/key/
system.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2026 ReifyDB
3
4use std::ops::Bound;
5
6use reifydb_codec::key::encoded::EncodedKey;
7use reifydb_macro::KeyCodec;
8use reifydb_runtime::version_epoch::EpochSeconds;
9use serde::{Deserialize, Serialize, de};
10
11use super::KeyTag;
12use crate::{
13	interface::catalog::id::{MigrationEventId, MigrationId, SequenceId},
14	key::{
15		any::{Field, KeyFields, Width},
16		bound::{TaggedKeyBound, TaggedKeyBoundRange},
17	},
18};
19
20#[derive(Debug, Clone, PartialEq, KeyCodec, Hash)]
21#[key(tag = SystemSequence)]
22pub struct SystemSequenceKey {
23	pub sequence: SequenceId,
24}
25
26impl SystemSequenceKey {
27	pub fn new(sequence: impl Into<SequenceId>) -> Self {
28		Self {
29			sequence: sequence.into(),
30		}
31	}
32
33	pub fn encoded(sequence: impl Into<SequenceId>) -> EncodedKey {
34		Self {
35			sequence: sequence.into(),
36		}
37		.encode()
38	}
39
40	pub fn full_scan() -> TaggedKeyBoundRange {
41		TaggedKeyBoundRange::kind(Self::TAG)
42	}
43}
44
45#[cfg(test)]
46pub mod system_sequence_key_tests {
47	use super::SystemSequenceKey;
48	use crate::interface::catalog::id::SequenceId;
49
50	#[test]
51	fn test_encode_decode() {
52		let key = SystemSequenceKey {
53			sequence: SequenceId(0xABCD),
54		};
55		let encoded = key.encode();
56		let expected = vec![0xFA, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x54, 0x32];
57		assert_eq!(encoded.as_slice(), expected);
58
59		let key = SystemSequenceKey::decode(&encoded).unwrap();
60		assert_eq!(key.sequence.0, 0xABCD);
61	}
62}
63
64#[derive(Debug, Clone, PartialEq, KeyCodec, Hash)]
65#[key(tag = SystemVersion)]
66pub struct SystemVersionKey {
67	#[key(repr = u8)]
68	pub version: SystemVersion,
69}
70
71#[repr(u8)]
72#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
73#[serde(try_from = "u8", into = "u8")]
74pub enum SystemVersion {
75	Storage = 0x01,
76}
77
78impl From<SystemVersion> for u8 {
79	fn from(version: SystemVersion) -> Self {
80		version as u8
81	}
82}
83impl TryFrom<u8> for SystemVersion {
84	type Error = de::value::Error;
85
86	fn try_from(value: u8) -> Result<Self, Self::Error> {
87		match value {
88			0x01 => Ok(Self::Storage),
89			_ => Err(de::Error::custom(format!("Invalid SystemVersion value: {value:#04x}"))),
90		}
91	}
92}
93
94impl SystemVersionKey {
95	pub fn new(version: SystemVersion) -> Self {
96		Self {
97			version,
98		}
99	}
100
101	pub fn encoded(version: SystemVersion) -> EncodedKey {
102		Self {
103			version,
104		}
105		.encode()
106	}
107}
108
109#[cfg(test)]
110pub mod system_version_key_tests {
111	use super::{SystemVersion, SystemVersionKey};
112
113	#[test]
114	fn test_encode_decode_storage_version() {
115		let key = SystemVersionKey {
116			version: SystemVersion::Storage,
117		};
118		let encoded = key.encode();
119		let expected = vec![0xF5, 0xFE];
120		assert_eq!(encoded.as_slice(), expected);
121
122		let key = SystemVersionKey::decode(&encoded).unwrap();
123		assert_eq!(key.version, SystemVersion::Storage);
124	}
125}
126
127#[derive(Debug, Clone, Copy, PartialEq, Eq, KeyCodec, Hash)]
128#[key(tag = VersionEpoch)]
129pub struct VersionEpochKey {
130	pub bucket: EpochSeconds,
131}
132
133impl VersionEpochKey {
134	pub fn new(bucket: EpochSeconds) -> Self {
135		Self {
136			bucket,
137		}
138	}
139
140	pub fn encoded(bucket: EpochSeconds) -> EncodedKey {
141		Self::new(bucket).encode()
142	}
143
144	fn bucket_bound(bucket: EpochSeconds) -> TaggedKeyBound {
145		TaggedKeyBound::prefix(Self::TAG, [Field::UDesc(Width::U64, bucket.seconds() as u128)])
146	}
147
148	pub fn floor_scan(target: EpochSeconds) -> TaggedKeyBoundRange {
149		TaggedKeyBoundRange {
150			start: Bound::Included(Self::bucket_bound(target)),
151			end: Bound::Included(Self::bucket_bound(EpochSeconds::new(0))),
152		}
153	}
154
155	pub fn older_than(cutoff: EpochSeconds) -> TaggedKeyBoundRange {
156		TaggedKeyBoundRange {
157			start: Bound::Excluded(Self::bucket_bound(cutoff)),
158			end: Bound::Included(Self::bucket_bound(EpochSeconds::new(0))),
159		}
160	}
161}
162
163#[cfg(test)]
164mod version_epoch_key_tests {
165	use std::ops::Bound;
166
167	use super::{EpochSeconds, VersionEpochKey};
168
169	fn sec(seconds: u64) -> EpochSeconds {
170		EpochSeconds::new(seconds)
171	}
172
173	#[test]
174	fn test_encode_decode() {
175		let key = VersionEpochKey {
176			bucket: sec(0x0123456789ABCDEF),
177		};
178		let encoded = key.encode();
179		let decoded = VersionEpochKey::decode(&encoded).unwrap();
180		assert_eq!(decoded.bucket, sec(0x0123456789ABCDEF));
181	}
182
183	#[test]
184	fn test_descending_order_so_newer_bucket_sorts_first() {
185		let older = VersionEpochKey::encoded(sec(100));
186		let newer = VersionEpochKey::encoded(sec(200));
187		assert!(
188			newer < older,
189			"a newer (larger) bucket must encode to smaller key bytes so floor_scan can take the first entry at-or-after the target"
190		);
191	}
192
193	#[test]
194	fn test_floor_scan_lower_bound_is_target_bucket() {
195		let target = sec(150);
196		let range = VersionEpochKey::floor_scan(target).encode();
197		assert_eq!(range.start, Bound::Included(VersionEpochKey::encoded(target)));
198		assert_eq!(range.end, Bound::Included(VersionEpochKey::encoded(sec(0))));
199		// A bucket exactly at the target is included; a bucket newer than the target is excluded.
200		assert!(VersionEpochKey::encoded(target) >= VersionEpochKey::encoded(target));
201		assert!(VersionEpochKey::encoded(sec(151)) < VersionEpochKey::encoded(target));
202	}
203}
204
205#[derive(Debug, Clone, PartialEq, KeyCodec, Hash)]
206#[key(tag = Migration)]
207pub struct MigrationKey {
208	pub migration: MigrationId,
209}
210
211impl MigrationKey {
212	pub fn new(migration: MigrationId) -> Self {
213		Self {
214			migration,
215		}
216	}
217
218	pub fn encoded(migration: impl Into<MigrationId>) -> EncodedKey {
219		Self::new(migration.into()).encode()
220	}
221
222	pub fn full_scan() -> TaggedKeyBoundRange {
223		TaggedKeyBoundRange::kind(Self::TAG)
224	}
225}
226
227#[cfg(test)]
228mod migration_key_tests {
229	use super::MigrationKey;
230	use crate::interface::catalog::id::MigrationId;
231
232	#[test]
233	fn test_encode_decode() {
234		let key = MigrationKey {
235			migration: MigrationId(0xABCD),
236		};
237		let encoded = key.encode();
238		let decoded = MigrationKey::decode(&encoded).unwrap();
239		assert_eq!(decoded.migration, MigrationId(0xABCD));
240	}
241}
242
243#[derive(Debug, Clone, PartialEq, KeyCodec, Hash)]
244#[key(tag = MigrationEvent)]
245pub struct MigrationEventKey {
246	pub event: MigrationEventId,
247}
248
249impl MigrationEventKey {
250	pub fn new(event: MigrationEventId) -> Self {
251		Self {
252			event,
253		}
254	}
255
256	pub fn encoded(event: impl Into<MigrationEventId>) -> EncodedKey {
257		Self::new(event.into()).encode()
258	}
259
260	pub fn full_scan() -> TaggedKeyBoundRange {
261		TaggedKeyBoundRange::kind(Self::TAG)
262	}
263}
264
265#[cfg(test)]
266mod migration_event_key_tests {
267	use super::MigrationEventKey;
268	use crate::interface::catalog::id::MigrationEventId;
269
270	#[test]
271	fn test_encode_decode() {
272		let key = MigrationEventKey {
273			event: MigrationEventId(0xABCD),
274		};
275		let encoded = key.encode();
276		let decoded = MigrationEventKey::decode(&encoded).unwrap();
277		assert_eq!(decoded.event, MigrationEventId(0xABCD));
278	}
279}