Skip to main content

reifydb_core/key/
partitioned_row.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2026 ReifyDB
3
4use std::collections::Bound;
5
6use reifydb_codec::key::{
7	deserializer::KeyDeserializer,
8	encoded::{EncodedKey, EncodedKeyRange},
9	serializer::KeySerializer,
10};
11use reifydb_value::value::{partition::Partition, row_number::RowNumber};
12
13use super::{EncodableKey, EncodableKeyRange, KeyKind};
14use crate::{
15	interface::catalog::object::ObjectId,
16	key::catalog::{KeyDeserializerCatalogExt, KeySerializerCatalogExt},
17};
18
19#[derive(Debug, Clone, PartialEq)]
20pub enum RowLocator {
21	Row(RowNumber),
22
23	Series {
24		variant_tag: Option<u8>,
25		key: u64,
26		sequence: u64,
27	},
28}
29
30#[derive(Debug, Clone, PartialEq)]
31pub struct PartitionedRowKey {
32	pub object: ObjectId,
33	pub partition: Partition,
34	pub locator: RowLocator,
35}
36
37impl PartitionedRowKey {
38	pub fn new(object: impl Into<ObjectId>, partition: Partition, locator: RowLocator) -> Self {
39		Self {
40			object: object.into(),
41			partition,
42			locator,
43		}
44	}
45
46	pub fn encoded(object: impl Into<ObjectId>, partition: Partition, locator: RowLocator) -> EncodedKey {
47		Self::new(object, partition, locator).encode()
48	}
49
50	pub fn object_of(key: &EncodedKey) -> Option<ObjectId> {
51		let mut de = KeyDeserializer::from_bytes(key.as_slice());
52		let kind: KeyKind = de.read_u8().ok()?.try_into().ok()?;
53		if kind != Self::KIND {
54			return None;
55		}
56		de.read_object_id().ok()
57	}
58
59	pub fn full_scan(object: impl Into<ObjectId>) -> EncodedKeyRange {
60		let object = object.into();
61		let mut start = KeySerializer::with_capacity(10);
62		start.extend_u8(Self::KIND as u8).extend_object_id(object);
63		let mut end = KeySerializer::with_capacity(10);
64		end.extend_u8(Self::KIND as u8).extend_object_id(object.prev());
65		EncodedKeyRange::start_end(Some(start.to_encoded_key()), Some(end.to_encoded_key()))
66	}
67
68	pub fn scan_range(object: impl Into<ObjectId>, last_key: Option<&EncodedKey>) -> EncodedKeyRange {
69		let object = object.into();
70		let start = match last_key {
71			Some(last) => Bound::Excluded(last.clone()),
72			None => {
73				let mut start = KeySerializer::with_capacity(10);
74				start.extend_u8(Self::KIND as u8).extend_object_id(object);
75				Bound::Included(start.to_encoded_key())
76			}
77		};
78		let mut end = KeySerializer::with_capacity(10);
79		end.extend_u8(Self::KIND as u8).extend_object_id(object.prev());
80		EncodedKeyRange::new(start, Bound::Included(end.to_encoded_key()))
81	}
82
83	pub fn partition_range(object: impl Into<ObjectId>, partition: Partition) -> EncodedKeyRange {
84		let object = object.into();
85		let mut prefix = KeySerializer::with_capacity(26);
86		prefix.extend_u8(Self::KIND as u8).extend_object_id(object).extend_u128(partition.0);
87		EncodedKeyRange::prefix(prefix.to_encoded_key().as_slice())
88	}
89
90	pub fn partition_scan_range(
91		object: impl Into<ObjectId>,
92		partition: Partition,
93		last_key: Option<&EncodedKey>,
94	) -> EncodedKeyRange {
95		let base = Self::partition_range(object, partition);
96		match last_key {
97			Some(last) => EncodedKeyRange::new(Bound::Excluded(last.clone()), base.end),
98			None => base,
99		}
100	}
101}
102
103impl EncodableKey for PartitionedRowKey {
104	const KIND: KeyKind = KeyKind::PartitionedRow;
105
106	fn encode(&self) -> EncodedKey {
107		let mut serializer = KeySerializer::with_capacity(32);
108		serializer.extend_u8(Self::KIND as u8).extend_object_id(self.object).extend_u128(self.partition.0);
109		match &self.locator {
110			RowLocator::Row(row) => {
111				serializer.extend_u64(row.0);
112			}
113			RowLocator::Series {
114				variant_tag,
115				key,
116				sequence,
117			} => {
118				match variant_tag {
119					Some(tag) => {
120						serializer.extend_u8(1u8).extend_u8(*tag);
121					}
122					None => {
123						serializer.extend_u8(0u8);
124					}
125				}
126				serializer.extend_u64(*key).extend_u64(*sequence);
127			}
128		}
129		serializer.to_encoded_key()
130	}
131
132	fn decode(key: &EncodedKey) -> Option<Self> {
133		let mut de = KeyDeserializer::from_bytes(key.as_slice());
134
135		let kind: KeyKind = de.read_u8().ok()?.try_into().ok()?;
136		if kind != Self::KIND {
137			return None;
138		}
139
140		let object = de.read_object_id().ok()?;
141		let partition = Partition(de.read_u128().ok()?);
142
143		let locator = match object {
144			ObjectId::Series(_) => {
145				let has_tag = de.read_u8().ok()?;
146				let variant_tag = if has_tag == 1 {
147					Some(de.read_u8().ok()?)
148				} else {
149					None
150				};
151				let key = de.read_u64().ok()?;
152				let sequence = de.read_u64().ok()?;
153				RowLocator::Series {
154					variant_tag,
155					key,
156					sequence,
157				}
158			}
159			_ => RowLocator::Row(RowNumber(de.read_u64().ok()?)),
160		};
161
162		Some(Self {
163			object,
164			partition,
165			locator,
166		})
167	}
168}
169
170#[derive(Debug, Clone, PartialEq)]
171pub struct PartitionedRowKeyRange {
172	pub object: ObjectId,
173}
174
175impl PartitionedRowKeyRange {
176	fn decode_key(key: &EncodedKey) -> Option<Self> {
177		let mut de = KeyDeserializer::from_bytes(key.as_slice());
178
179		let kind: KeyKind = de.read_u8().ok()?.try_into().ok()?;
180		if kind != Self::KIND {
181			return None;
182		}
183
184		let object = de.read_object_id().ok()?;
185
186		Some(PartitionedRowKeyRange {
187			object,
188		})
189	}
190}
191
192impl EncodableKeyRange for PartitionedRowKeyRange {
193	const KIND: KeyKind = KeyKind::PartitionedRow;
194
195	fn start(&self) -> Option<EncodedKey> {
196		let mut serializer = KeySerializer::with_capacity(10);
197		serializer.extend_u8(Self::KIND as u8).extend_object_id(self.object);
198		Some(serializer.to_encoded_key())
199	}
200
201	fn end(&self) -> Option<EncodedKey> {
202		let mut serializer = KeySerializer::with_capacity(10);
203		serializer.extend_u8(Self::KIND as u8).extend_object_id(self.object.prev());
204		Some(serializer.to_encoded_key())
205	}
206
207	fn decode(range: &EncodedKeyRange) -> (Option<Self>, Option<Self>)
208	where
209		Self: Sized,
210	{
211		let start_key = match &range.start {
212			Bound::Included(key) | Bound::Excluded(key) => Self::decode_key(key),
213			Bound::Unbounded => None,
214		};
215
216		let end_key = match &range.end {
217			Bound::Included(key) | Bound::Excluded(key) => Self::decode_key(key),
218			Bound::Unbounded => None,
219		};
220
221		(start_key, end_key)
222	}
223}
224
225#[cfg(test)]
226mod tests {
227	use std::ops::RangeBounds;
228
229	use reifydb_value::value::{Value, partition::Partition, row_number::RowNumber};
230
231	use super::{EncodableKey, PartitionedRowKey, RowLocator};
232	use crate::interface::catalog::{
233		id::{SeriesId, TableId},
234		object::ObjectId,
235	};
236
237	fn part(v: &str) -> Partition {
238		Partition::of(&[Value::Utf8(v.to_string())])
239	}
240
241	#[test]
242	fn test_table_roundtrip() {
243		let key = PartitionedRowKey {
244			object: ObjectId::Table(TableId(7)),
245			partition: part("us"),
246			locator: RowLocator::Row(RowNumber(42)),
247		};
248		let decoded = PartitionedRowKey::decode(&key.encode()).unwrap();
249		assert_eq!(decoded, key);
250	}
251
252	#[test]
253	fn test_series_roundtrip_with_tag() {
254		let key = PartitionedRowKey {
255			object: ObjectId::Series(SeriesId(3)),
256			partition: part("btc"),
257			locator: RowLocator::Series {
258				variant_tag: Some(5),
259				key: 1_700_000_000,
260				sequence: 9,
261			},
262		};
263		let decoded = PartitionedRowKey::decode(&key.encode()).unwrap();
264		assert_eq!(decoded, key);
265	}
266
267	#[test]
268	fn test_series_roundtrip_without_tag() {
269		let key = PartitionedRowKey {
270			object: ObjectId::Series(SeriesId(3)),
271			partition: part("eth"),
272			locator: RowLocator::Series {
273				variant_tag: None,
274				key: 100,
275				sequence: 0,
276			},
277		};
278		let decoded = PartitionedRowKey::decode(&key.encode()).unwrap();
279		assert_eq!(decoded, key);
280	}
281
282	#[test]
283	fn test_object_of() {
284		let key = PartitionedRowKey::encoded(
285			ObjectId::Table(TableId(42)),
286			part("us"),
287			RowLocator::Row(RowNumber(1)),
288		);
289		assert_eq!(PartitionedRowKey::object_of(&key), Some(ObjectId::Table(TableId(42))));
290	}
291
292	#[test]
293	fn test_partition_rows_cluster_together() {
294		let object = ObjectId::Table(TableId(1));
295		let us_a = PartitionedRowKey::encoded(object, part("us"), RowLocator::Row(RowNumber(1)));
296		let us_b = PartitionedRowKey::encoded(object, part("us"), RowLocator::Row(RowNumber(2)));
297		let eu = PartitionedRowKey::encoded(object, part("eu"), RowLocator::Row(RowNumber(1)));
298
299		let mut keys = [us_a.clone(), us_b.clone(), eu.clone()];
300		keys.sort();
301		let us_positions: Vec<usize> =
302			keys.iter().enumerate().filter(|(_, k)| **k == us_a || **k == us_b).map(|(i, _)| i).collect();
303		assert_eq!(us_positions[1] - us_positions[0], 1, "us partition rows must be contiguous");
304	}
305
306	#[test]
307	fn test_partition_range_contains_only_its_partition() {
308		let object = ObjectId::Table(TableId(1));
309		let range = PartitionedRowKey::partition_range(object, part("us"));
310		let us = PartitionedRowKey::encoded(object, part("us"), RowLocator::Row(RowNumber(500)));
311		let eu = PartitionedRowKey::encoded(object, part("eu"), RowLocator::Row(RowNumber(1)));
312		assert!(range.contains(&us), "us row must be inside the us partition range");
313		assert!(!range.contains(&eu), "eu row must be outside the us partition range");
314	}
315}