Skip to main content

reifydb_core/key/
column.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2026 ReifyDB
3
4use reifydb_codec::key::encoded::EncodedKey;
5use reifydb_macro::KeyCodec;
6use reifydb_value::value::partition::Partition;
7
8use super::KeyTag;
9use crate::{
10	interface::catalog::{
11		id::{ColumnBlockId, ColumnId, SeriesId, TableId},
12		object::ObjectId,
13	},
14	key::{
15		any::{Field, KeyFields, Width},
16		bound::{TaggedKeyBoundRange, object_fields},
17		catalog::{KeyDeserializerCatalogExt, KeySerializerCatalogExt},
18	},
19};
20
21#[derive(Debug, Clone, PartialEq, KeyCodec, Hash)]
22#[key(tag = Column)]
23pub struct ColumnKey {
24	pub object: ObjectId,
25	pub column: ColumnId,
26}
27
28impl ColumnKey {
29	pub fn new(object: impl Into<ObjectId>, column: impl Into<ColumnId>) -> Self {
30		Self {
31			object: object.into(),
32			column: column.into(),
33		}
34	}
35
36	pub fn encoded(object: impl Into<ObjectId>, column: impl Into<ColumnId>) -> EncodedKey {
37		Self::new(object, column).encode()
38	}
39
40	pub fn full_scan(object: impl Into<ObjectId>) -> TaggedKeyBoundRange {
41		TaggedKeyBoundRange::prefix(Self::TAG, object_fields(object.into()))
42	}
43}
44
45#[cfg(test)]
46pub mod column_key_tests {
47	use crate::{
48		interface::catalog::{id::ColumnId, object::ObjectId},
49		key::column::ColumnKey,
50	};
51
52	#[test]
53	fn test_encode_decode() {
54		let key = ColumnKey {
55			object: ObjectId::table(0xABCD),
56			column: ColumnId(0x123456789ABCDEF0),
57		};
58		let encoded = key.encode();
59
60		let expected: Vec<u8> = vec![
61			0xF8, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x54, 0x32, 0xED, 0xCB, 0xA9, 0x87, 0x65, 0x43,
62			0x21, 0x0F,
63		];
64
65		assert_eq!(encoded.as_slice(), expected);
66
67		let key = ColumnKey::decode(&encoded).unwrap();
68		assert_eq!(key.object, 0xABCD);
69		assert_eq!(key.column, 0x123456789ABCDEF0);
70	}
71
72	#[test]
73	fn test_order_preserving() {
74		let key1 = ColumnKey {
75			object: ObjectId::table(1),
76			column: ColumnId(100),
77		};
78		let key2 = ColumnKey {
79			object: ObjectId::table(1),
80			column: ColumnId(200),
81		};
82		let key3 = ColumnKey {
83			object: ObjectId::table(2),
84			column: ColumnId(0),
85		};
86
87		let encoded1 = key1.encode();
88		let encoded2 = key2.encode();
89		let encoded3 = key3.encode();
90
91		assert!(encoded3 < encoded2, "ordering not preserved");
92		assert!(encoded2 < encoded1, "ordering not preserved");
93	}
94}
95
96#[derive(Debug, Clone, Copy, PartialEq, Eq, KeyCodec, Hash)]
97#[key(tag = ColumnSequence)]
98pub struct ColumnSequenceKey {
99	pub object: ObjectId,
100	pub column: ColumnId,
101}
102
103impl ColumnSequenceKey {
104	pub fn new(object: impl Into<ObjectId>, column: impl Into<ColumnId>) -> Self {
105		Self {
106			object: object.into(),
107			column: column.into(),
108		}
109	}
110
111	pub fn encoded(object: impl Into<ObjectId>, column: impl Into<ColumnId>) -> EncodedKey {
112		Self::new(object, column).encode()
113	}
114}
115
116#[cfg(test)]
117pub mod column_sequence_key_tests {
118	use reifydb_codec::key::encoded::EncodedKey;
119
120	use super::ColumnSequenceKey;
121	use crate::interface::catalog::{id::ColumnId, object::ObjectId};
122
123	#[test]
124	fn test_encode_decode() {
125		let key = ColumnSequenceKey {
126			object: ObjectId::table(0x1234),
127			column: ColumnId(0x5678),
128		};
129		let encoded = key.encode();
130
131		assert_eq!(encoded[0], 0xF1);
132
133		let decoded = ColumnSequenceKey::decode(&encoded).unwrap();
134		assert_eq!(decoded.object, ObjectId::table(0x1234));
135		assert_eq!(decoded.column, ColumnId(0x5678));
136	}
137
138	#[test]
139	fn test_decode_invalid_version() {
140		let mut encoded = vec![0xFF];
141		encoded.push(0x0E);
142		encoded.extend(&[0; 16]);
143
144		let decoded = ColumnSequenceKey::decode(&EncodedKey::new(encoded));
145		assert!(decoded.is_none());
146	}
147
148	#[test]
149	fn test_decode_invalid_kind() {
150		let mut encoded = vec![0x01];
151		encoded.push(0xFF);
152		encoded.extend(&[0; 16]);
153
154		let decoded = ColumnSequenceKey::decode(&EncodedKey::new(encoded));
155		assert!(decoded.is_none());
156	}
157
158	#[test]
159	fn test_decode_invalid_length() {
160		let encoded = vec![0x01, 0x0E];
161		let decoded = ColumnSequenceKey::decode(&EncodedKey::new(encoded));
162		assert!(decoded.is_none());
163	}
164}
165
166#[derive(Debug, Clone, PartialEq, KeyCodec, Hash)]
167#[key(tag = ColumnBlock)]
168pub struct ColumnBlockKey {
169	pub block: ColumnBlockId,
170}
171
172impl ColumnBlockKey {
173	pub fn new(block: ColumnBlockId) -> Self {
174		Self {
175			block,
176		}
177	}
178
179	pub fn encoded(block: impl Into<ColumnBlockId>) -> EncodedKey {
180		Self::new(block.into()).encode()
181	}
182
183	pub fn full_scan() -> TaggedKeyBoundRange {
184		TaggedKeyBoundRange::kind(Self::TAG)
185	}
186}
187
188#[derive(Debug, Clone, PartialEq, KeyCodec, Hash)]
189#[key(tag = SeriesColumnBlock)]
190pub struct SeriesColumnBlockKey {
191	pub series: SeriesId,
192	pub partition: Partition,
193	pub block: ColumnBlockId,
194}
195
196impl SeriesColumnBlockKey {
197	pub fn new(series: SeriesId, partition: Partition, block: ColumnBlockId) -> Self {
198		Self {
199			series,
200			partition,
201			block,
202		}
203	}
204
205	pub fn encoded(
206		series: impl Into<SeriesId>,
207		partition: Partition,
208		block: impl Into<ColumnBlockId>,
209	) -> EncodedKey {
210		Self::new(series.into(), partition, block.into()).encode()
211	}
212
213	pub fn full_scan(series: SeriesId) -> TaggedKeyBoundRange {
214		TaggedKeyBoundRange::prefix(Self::TAG, [Field::UDesc(Width::U64, series.0 as u128)])
215	}
216
217	pub fn partition_scan(series: SeriesId, partition: Partition) -> TaggedKeyBoundRange {
218		TaggedKeyBoundRange::prefix(
219			Self::TAG,
220			[Field::UDesc(Width::U64, series.0 as u128), Field::UDesc(Width::U128, partition.0)],
221		)
222	}
223}
224
225#[derive(Debug, Clone, PartialEq, KeyCodec, Hash)]
226#[key(tag = TableColumnBlock)]
227pub struct TableColumnBlockKey {
228	pub table: TableId,
229	pub block: ColumnBlockId,
230}
231
232impl TableColumnBlockKey {
233	pub fn new(table: TableId, block: ColumnBlockId) -> Self {
234		Self {
235			table,
236			block,
237		}
238	}
239
240	pub fn encoded(table: impl Into<TableId>, block: impl Into<ColumnBlockId>) -> EncodedKey {
241		Self::new(table.into(), block.into()).encode()
242	}
243
244	pub fn full_scan(table: TableId) -> TaggedKeyBoundRange {
245		TaggedKeyBoundRange::prefix(Self::TAG, [Field::UDesc(Width::U64, table.0 as u128)])
246	}
247}
248
249#[cfg(test)]
250pub mod column_block_key_tests {
251	use std::ops::Bound;
252
253	use reifydb_codec::key::encoded::EncodedKey;
254
255	use super::*;
256
257	fn contains(range: &TaggedKeyBoundRange, key: &EncodedKey) -> bool {
258		let start = match &range.start {
259			Bound::Included(b) => *key >= b.encode(),
260			Bound::Excluded(b) => *key > b.encode(),
261			Bound::Unbounded => true,
262		};
263		let end = match &range.end {
264			Bound::Included(b) => *key <= b.encode(),
265			Bound::Excluded(b) => *key < b.encode(),
266			Bound::Unbounded => true,
267		};
268		start && end
269	}
270
271	#[test]
272	fn a_block_key_reports_its_own_kind_from_its_first_byte() {
273		// the leading version byte made KeyTag::of read every block key as a namespace key, so the
274		// metrics parser and the entry classifier both routed them to an owner they never belonged to
275		assert_eq!(KeyTag::of(ColumnBlockKey::encoded(ColumnBlockId(1))), Some(KeyTag::ColumnBlock));
276		assert_eq!(
277			KeyTag::of(SeriesColumnBlockKey::encoded(SeriesId(1), Partition(3), ColumnBlockId(2))),
278			Some(KeyTag::SeriesColumnBlock)
279		);
280		assert_eq!(
281			KeyTag::of(TableColumnBlockKey::encoded(TableId(1), ColumnBlockId(2))),
282			Some(KeyTag::TableColumnBlock)
283		);
284	}
285
286	#[test]
287	fn test_column_block_key_encode_decode() {
288		let key = ColumnBlockKey {
289			block: ColumnBlockId(0x1234),
290		};
291		let encoded = key.encode();
292		let decoded = ColumnBlockKey::decode(&encoded).unwrap();
293		assert_eq!(decoded.block, key.block);
294	}
295
296	#[test]
297	fn test_column_block_key_full_scan() {
298		let range = ColumnBlockKey::full_scan();
299		assert!(matches!(range.start, Bound::Included(_) | Bound::Excluded(_)));
300		assert!(matches!(range.end, Bound::Included(_) | Bound::Excluded(_)));
301	}
302
303	#[test]
304	fn test_series_column_block_key_encode_decode() {
305		let key = SeriesColumnBlockKey {
306			series: SeriesId(42),
307			partition: Partition(0x0123_4567_89AB_CDEF_FEDC_BA98_7654_3210),
308			block: ColumnBlockId(99),
309		};
310		let encoded = key.encode();
311		let decoded = SeriesColumnBlockKey::decode(&encoded).unwrap();
312		assert_eq!(decoded.series, key.series);
313		assert_eq!(decoded.partition, key.partition);
314		assert_eq!(decoded.block, key.block);
315	}
316
317	#[test]
318	fn test_series_column_block_partition_scan_excludes_other_partitions() {
319		// the scoped listing reads this range directly, so a prefix that still covers a
320		// neighbouring partition would hand the scan another partition's blocks
321		let range = SeriesColumnBlockKey::partition_scan(SeriesId(42), Partition(7));
322		let inside = SeriesColumnBlockKey::encoded(SeriesId(42), Partition(7), ColumnBlockId(1));
323		let other_partition = SeriesColumnBlockKey::encoded(SeriesId(42), Partition(8), ColumnBlockId(1));
324		let other_series = SeriesColumnBlockKey::encoded(SeriesId(43), Partition(7), ColumnBlockId(1));
325
326		assert!(contains(&range, &inside), "the scanned partition must be inside its own range");
327		assert!(!contains(&range, &other_partition), "a different partition must fall outside");
328		assert!(!contains(&range, &other_series), "a different series must fall outside");
329	}
330
331	#[test]
332	fn test_series_column_block_full_scan_spans_every_partition() {
333		// a query with no partition filter still needs every block of the series
334		let range = SeriesColumnBlockKey::full_scan(SeriesId(42));
335		for partition in [Partition(0), Partition(7), Partition(u128::MAX)] {
336			let key = SeriesColumnBlockKey::encoded(SeriesId(42), partition, ColumnBlockId(1));
337			assert!(contains(&range, &key), "partition {:?} must be inside the full scan", partition);
338		}
339		let other_series = SeriesColumnBlockKey::encoded(SeriesId(43), Partition(7), ColumnBlockId(1));
340		assert!(!contains(&range, &other_series), "a different series must fall outside");
341	}
342
343	#[test]
344	fn test_table_column_block_key_encode_decode() {
345		let key = TableColumnBlockKey {
346			table: TableId(42),
347			block: ColumnBlockId(99),
348		};
349		let encoded = key.encode();
350		let decoded = TableColumnBlockKey::decode(&encoded).unwrap();
351		assert_eq!(decoded.table, key.table);
352		assert_eq!(decoded.block, key.block);
353	}
354}
355
356#[derive(Debug, Clone, PartialEq, KeyCodec, Hash)]
357#[key(tag = Columns)]
358pub struct ColumnsKey {
359	pub column: ColumnId,
360}
361
362impl ColumnsKey {
363	pub fn new(column: impl Into<ColumnId>) -> Self {
364		Self {
365			column: column.into(),
366		}
367	}
368
369	pub fn encoded(column: impl Into<ColumnId>) -> EncodedKey {
370		Self::new(column).encode()
371	}
372
373	pub fn full_scan() -> TaggedKeyBoundRange {
374		TaggedKeyBoundRange::kind(Self::TAG)
375	}
376}
377
378#[cfg(test)]
379pub mod columns_key_tests {
380	use super::ColumnsKey;
381	use crate::interface::catalog::id::ColumnId;
382
383	#[test]
384	fn test_encode_decode() {
385		let key = ColumnsKey {
386			column: ColumnId(0xABCD),
387		};
388		let encoded = key.encode();
389		let expected = vec![0xF9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x54, 0x32];
390		assert_eq!(encoded.as_slice(), expected);
391
392		let key = ColumnsKey::decode(&encoded).unwrap();
393		assert_eq!(key.column, 0xABCD);
394	}
395}