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