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, PartialEq, KeyCodec, Hash)]
128#[key(tag = TransactionVersion)]
129pub struct TransactionVersionKey {}
130
131impl TransactionVersionKey {
132	pub fn encoded() -> EncodedKey {
133		Self {}.encode()
134	}
135}
136
137#[cfg(test)]
138pub mod transaction_version_key_tests {
139	use super::TransactionVersionKey;
140
141	#[test]
142	fn test_encode_decode() {
143		let key = TransactionVersionKey {};
144		let encoded = key.encode();
145		let expected = vec![0xF4];
146		assert_eq!(encoded.as_slice(), expected);
147
148		TransactionVersionKey::decode(&encoded).unwrap();
149	}
150}
151
152#[derive(Debug, Clone, Copy, PartialEq, Eq, KeyCodec, Hash)]
153#[key(tag = VersionEpoch)]
154pub struct VersionEpochKey {
155	pub bucket: EpochSeconds,
156}
157
158impl VersionEpochKey {
159	pub fn new(bucket: EpochSeconds) -> Self {
160		Self {
161			bucket,
162		}
163	}
164
165	pub fn encoded(bucket: EpochSeconds) -> EncodedKey {
166		Self::new(bucket).encode()
167	}
168
169	fn bucket_bound(bucket: EpochSeconds) -> TaggedKeyBound {
170		TaggedKeyBound::prefix(Self::TAG, [Field::UDesc(Width::U64, bucket.seconds() as u128)])
171	}
172
173	pub fn floor_scan(target: EpochSeconds) -> TaggedKeyBoundRange {
174		TaggedKeyBoundRange {
175			start: Bound::Included(Self::bucket_bound(target)),
176			end: Bound::Included(Self::bucket_bound(EpochSeconds::new(0))),
177		}
178	}
179
180	pub fn older_than(cutoff: EpochSeconds) -> TaggedKeyBoundRange {
181		TaggedKeyBoundRange {
182			start: Bound::Excluded(Self::bucket_bound(cutoff)),
183			end: Bound::Included(Self::bucket_bound(EpochSeconds::new(0))),
184		}
185	}
186}
187
188#[cfg(test)]
189mod version_epoch_key_tests {
190	use std::ops::Bound;
191
192	use super::{EpochSeconds, VersionEpochKey};
193
194	fn sec(seconds: u64) -> EpochSeconds {
195		EpochSeconds::new(seconds)
196	}
197
198	#[test]
199	fn test_encode_decode() {
200		let key = VersionEpochKey {
201			bucket: sec(0x0123456789ABCDEF),
202		};
203		let encoded = key.encode();
204		let decoded = VersionEpochKey::decode(&encoded).unwrap();
205		assert_eq!(decoded.bucket, sec(0x0123456789ABCDEF));
206	}
207
208	#[test]
209	fn test_descending_order_so_newer_bucket_sorts_first() {
210		let older = VersionEpochKey::encoded(sec(100));
211		let newer = VersionEpochKey::encoded(sec(200));
212		assert!(
213			newer < older,
214			"a newer (larger) bucket must encode to smaller key bytes so floor_scan can take the first entry at-or-after the target"
215		);
216	}
217
218	#[test]
219	fn test_floor_scan_lower_bound_is_target_bucket() {
220		let target = sec(150);
221		let range = VersionEpochKey::floor_scan(target).encode();
222		assert_eq!(range.start, Bound::Included(VersionEpochKey::encoded(target)));
223		assert_eq!(range.end, Bound::Included(VersionEpochKey::encoded(sec(0))));
224		// A bucket exactly at the target is included; a bucket newer than the target is excluded.
225		assert!(VersionEpochKey::encoded(target) >= VersionEpochKey::encoded(target));
226		assert!(VersionEpochKey::encoded(sec(151)) < VersionEpochKey::encoded(target));
227	}
228}
229
230#[derive(Debug, Clone, PartialEq, KeyCodec, Hash)]
231#[key(tag = Migration)]
232pub struct MigrationKey {
233	pub migration: MigrationId,
234}
235
236impl MigrationKey {
237	pub fn new(migration: MigrationId) -> Self {
238		Self {
239			migration,
240		}
241	}
242
243	pub fn encoded(migration: impl Into<MigrationId>) -> EncodedKey {
244		Self::new(migration.into()).encode()
245	}
246
247	pub fn full_scan() -> TaggedKeyBoundRange {
248		TaggedKeyBoundRange::kind(Self::TAG)
249	}
250}
251
252#[cfg(test)]
253mod migration_key_tests {
254	use super::MigrationKey;
255	use crate::interface::catalog::id::MigrationId;
256
257	#[test]
258	fn test_encode_decode() {
259		let key = MigrationKey {
260			migration: MigrationId(0xABCD),
261		};
262		let encoded = key.encode();
263		let decoded = MigrationKey::decode(&encoded).unwrap();
264		assert_eq!(decoded.migration, MigrationId(0xABCD));
265	}
266}
267
268#[derive(Debug, Clone, PartialEq, KeyCodec, Hash)]
269#[key(tag = MigrationEvent)]
270pub struct MigrationEventKey {
271	pub event: MigrationEventId,
272}
273
274impl MigrationEventKey {
275	pub fn new(event: MigrationEventId) -> Self {
276		Self {
277			event,
278		}
279	}
280
281	pub fn encoded(event: impl Into<MigrationEventId>) -> EncodedKey {
282		Self::new(event.into()).encode()
283	}
284
285	pub fn full_scan() -> TaggedKeyBoundRange {
286		TaggedKeyBoundRange::kind(Self::TAG)
287	}
288}
289
290#[cfg(test)]
291mod migration_event_key_tests {
292	use super::MigrationEventKey;
293	use crate::interface::catalog::id::MigrationEventId;
294
295	#[test]
296	fn test_encode_decode() {
297		let key = MigrationEventKey {
298			event: MigrationEventId(0xABCD),
299		};
300		let encoded = key.encode();
301		let decoded = MigrationEventKey::decode(&encoded).unwrap();
302		assert_eq!(decoded.event, MigrationEventId(0xABCD));
303	}
304}