Skip to main content

reifydb_codec/row/shape/
mod.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2026 ReifyDB
3
4pub mod fingerprint;
5pub mod values;
6
7use std::{
8	fmt,
9	fmt::Debug,
10	iter,
11	ops::Deref,
12	ptr,
13	sync::{Arc, OnceLock},
14};
15
16use reifydb_value::{
17	reifydb_assertions,
18	value::{constraint::TypeConstraint, datetime::DateTime, value_type::ValueType},
19};
20use serde::{Deserialize, Serialize};
21
22use super::bytes::{
23	CATALOG_HEADER_SIZE, EncodedRowBuilder, QUEUE_ATTEMPT_HEADER_SIZE, QUEUE_DEDUPLICATION_HEADER_SIZE,
24	QUEUE_HEADER_SIZE, RowBuilder, SHAPE_HEADER_SIZE, read_created_at, read_defined_at, read_storage_time,
25	read_updated_at, write_fingerprint,
26};
27use crate::row::{
28	catalog::EncodedCatalogRowBuilder,
29	operator::{
30		EncodedOperatorRowBuilder, OPERATOR_HEADER_SIZE, read_time as read_operator_time,
31		write_time as write_operator_time,
32	},
33	pod::{EncodedPodRowBuilder, POD_HEADER_SIZE},
34	queue::EncodedQueueRowBuilder,
35	queue_attempt::EncodedQueueAttemptRowBuilder,
36	queue_deduplication::EncodedQueueDeduplicationRowBuilder,
37	ringbuffer::EncodedRingBufferRowBuilder,
38	series::EncodedSeriesRowBuilder,
39	shape::fingerprint::{RowShapeFingerprint, compute_fingerprint},
40	table::EncodedTableRowBuilder,
41};
42
43const PACKED_MODE_DYNAMIC: u128 = 0x80000000000000000000000000000000;
44const PACKED_MODE_MASK: u128 = 0x80000000000000000000000000000000;
45const PACKED_OFFSET_MASK: u128 = 0x0000000000000000FFFFFFFFFFFFFFFF;
46const PACKED_LENGTH_MASK: u128 = 0x7FFFFFFFFFFFFFFF0000000000000000;
47
48#[repr(u8)]
49#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
50pub enum RowFamily {
51	Catalog = 0x01,
52	Pod = 0x02,
53	Table = 0x03,
54	Series = 0x04,
55	RingBuffer = 0x05,
56	Queue = 0x06,
57	Operator = 0x07,
58	QueueAttempt = 0x08,
59	QueueDeduplication = 0x09,
60}
61
62impl RowFamily {
63	pub const fn header_size(self) -> usize {
64		match self {
65			Self::Catalog => CATALOG_HEADER_SIZE,
66			Self::Pod => POD_HEADER_SIZE,
67			Self::Table => SHAPE_HEADER_SIZE,
68			Self::Series => SHAPE_HEADER_SIZE,
69			Self::RingBuffer => SHAPE_HEADER_SIZE,
70			Self::Queue => QUEUE_HEADER_SIZE,
71			Self::Operator => OPERATOR_HEADER_SIZE,
72			Self::QueueAttempt => QUEUE_ATTEMPT_HEADER_SIZE,
73			Self::QueueDeduplication => QUEUE_DEDUPLICATION_HEADER_SIZE,
74		}
75	}
76
77	#[inline]
78	pub fn updated_at(self, row: &[u8]) -> DateTime {
79		match self {
80			Self::Table
81			| Self::Series
82			| Self::RingBuffer
83			| Self::Queue
84			| Self::QueueAttempt
85			| Self::QueueDeduplication => read_updated_at(row),
86			_ => panic!("{self:?} rows carry no updated_at"),
87		}
88	}
89
90	pub const fn from_u8(value: u8) -> Option<Self> {
91		match value {
92			0x01 => Some(Self::Catalog),
93			0x02 => Some(Self::Pod),
94			0x03 => Some(Self::Table),
95			0x04 => Some(Self::Series),
96			0x05 => Some(Self::RingBuffer),
97			0x06 => Some(Self::Queue),
98			0x07 => Some(Self::Operator),
99			0x08 => Some(Self::QueueAttempt),
100			0x09 => Some(Self::QueueDeduplication),
101			_ => None,
102		}
103	}
104}
105
106#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
107pub struct RowShapeField {
108	pub name: String,
109
110	pub constraint: TypeConstraint,
111
112	pub offset: u32,
113
114	pub size: u32,
115}
116
117impl RowShapeField {
118	pub fn new(name: impl Into<String>, constraint: TypeConstraint) -> Self {
119		let storage_type = constraint.storage_type();
120		Self {
121			name: name.into(),
122			constraint,
123			offset: 0,
124			size: storage_type.size() as u32,
125		}
126	}
127
128	pub fn unconstrained(name: impl Into<String>, field_type: ValueType) -> Self {
129		Self::new(name, TypeConstraint::unconstrained(field_type))
130	}
131}
132
133pub struct RowShape(Arc<Inner>);
134
135#[derive(Debug, Serialize, Deserialize)]
136pub struct Inner {
137	pub fingerprint: RowShapeFingerprint,
138
139	pub family: RowFamily,
140
141	pub fields: Vec<RowShapeField>,
142
143	#[serde(skip)]
144	cached_layout: OnceLock<usize>,
145}
146
147impl PartialEq for Inner {
148	fn eq(&self, other: &Self) -> bool {
149		self.fingerprint == other.fingerprint && self.family == other.family && self.fields == other.fields
150	}
151}
152
153impl Eq for Inner {}
154
155impl Deref for RowShape {
156	type Target = Inner;
157
158	fn deref(&self) -> &Self::Target {
159		&self.0
160	}
161}
162
163impl Clone for RowShape {
164	fn clone(&self) -> Self {
165		Self(self.0.clone())
166	}
167}
168
169impl Debug for RowShape {
170	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
171		self.0.fmt(f)
172	}
173}
174
175impl PartialEq for RowShape {
176	fn eq(&self, other: &Self) -> bool {
177		self.0.as_ref() == other.0.as_ref()
178	}
179}
180
181impl Eq for RowShape {}
182
183impl RowShape {
184	pub fn new(family: RowFamily, fields: Vec<RowShapeField>) -> Self {
185		let fields = Self::compute_layout(family, fields);
186		let fingerprint = compute_fingerprint(family, &fields);
187
188		Self(Arc::new(Inner {
189			fingerprint,
190			family,
191			fields,
192			cached_layout: OnceLock::new(),
193		}))
194	}
195
196	pub fn from_parts(family: RowFamily, fingerprint: RowShapeFingerprint, fields: Vec<RowShapeField>) -> Self {
197		Self(Arc::new(Inner {
198			fingerprint,
199			family,
200			fields,
201			cached_layout: OnceLock::new(),
202		}))
203	}
204
205	pub fn family(&self) -> RowFamily {
206		self.family
207	}
208
209	pub fn header_size(&self) -> usize {
210		self.family.header_size()
211	}
212
213	pub fn fingerprint(&self) -> RowShapeFingerprint {
214		self.fingerprint
215	}
216
217	pub fn fields(&self) -> &[RowShapeField] {
218		&self.fields
219	}
220
221	pub fn field_count(&self) -> usize {
222		self.fields.len()
223	}
224
225	pub fn find_field(&self, name: &str) -> Option<&RowShapeField> {
226		self.fields.iter().find(|f| f.name == name)
227	}
228
229	pub fn find_field_index(&self, name: &str) -> Option<usize> {
230		self.fields.iter().position(|f| f.name == name)
231	}
232
233	pub fn get_field(&self, index: usize) -> Option<&RowShapeField> {
234		self.fields.get(index)
235	}
236
237	pub fn get_field_name(&self, index: usize) -> Option<&str> {
238		self.fields.get(index).map(|f| f.name.as_str())
239	}
240
241	pub fn field_names(&self) -> impl Iterator<Item = &str> {
242		self.fields.iter().map(|f| f.name.as_str())
243	}
244
245	fn compute_layout(family: RowFamily, mut fields: Vec<RowShapeField>) -> Vec<RowShapeField> {
246		let bitvec_size = fields.len().div_ceil(8);
247		let mut offset: u32 = (family.header_size() + bitvec_size) as u32;
248
249		for field in fields.iter_mut() {
250			field.size = field.constraint.storage_type().size() as u32;
251			field.offset = offset;
252			offset += field.size;
253		}
254
255		fields
256	}
257
258	pub fn bitvec_size(&self) -> usize {
259		self.fields.len().div_ceil(8)
260	}
261
262	pub fn data_offset(&self) -> usize {
263		self.header_size() + self.bitvec_size()
264	}
265
266	#[inline]
267	pub fn is_defined(&self, row: &[u8], index: usize) -> bool {
268		read_defined_at(row, self.header_size(), index)
269	}
270
271	#[inline]
272	pub(crate) fn set_valid(&self, row: &mut impl RowBuilder, index: usize, valid: bool) {
273		row.set_valid_at(self.header_size(), index, valid);
274	}
275
276	#[inline]
277	pub fn time(&self, row: &[u8]) -> Option<DateTime> {
278		match self.family {
279			RowFamily::Pod => None,
280			RowFamily::Operator => read_operator_time(row),
281			_ => read_storage_time(row),
282		}
283	}
284
285	#[inline]
286	pub fn created_at(&self, row: &[u8]) -> DateTime {
287		match self.family {
288			RowFamily::Pod => panic!("pod rows carry no created_at"),
289			RowFamily::Operator => panic!("operator rows carry no created_at"),
290			_ => read_created_at(row),
291		}
292	}
293
294	#[inline]
295	pub fn updated_at(&self, row: &[u8]) -> DateTime {
296		match self.family {
297			RowFamily::Pod => panic!("pod rows carry no updated_at"),
298			RowFamily::Operator => panic!("operator rows carry no updated_at"),
299			_ => read_updated_at(row),
300		}
301	}
302
303	fn get_cached_layout(&self) -> usize {
304		*self.cached_layout.get_or_init(|| match self.fields.last() {
305			Some(last) => last.offset as usize + last.size as usize,
306			None => self.header_size() + self.bitvec_size(),
307		})
308	}
309
310	pub fn total_static_size(&self) -> usize {
311		self.get_cached_layout()
312	}
313
314	pub fn dynamic_section_start(&self) -> usize {
315		self.total_static_size()
316	}
317
318	pub fn dynamic_section_size(&self, row: &[u8]) -> usize {
319		row.len().saturating_sub(self.total_static_size())
320	}
321
322	pub(crate) fn read_dynamic_ref(&self, row: &[u8], index: usize) -> Option<(usize, usize)> {
323		if !self.is_defined(row, index) {
324			return None;
325		}
326		let field = &self.fields()[index];
327		match field.constraint.get_type().inner_type() {
328			ValueType::Utf8 | ValueType::Blob | ValueType::Any => {
329				let ref_slice = &row[field.offset as usize..field.offset as usize + 8];
330				let offset =
331					u32::from_le_bytes([ref_slice[0], ref_slice[1], ref_slice[2], ref_slice[3]])
332						as usize;
333				let length =
334					u32::from_le_bytes([ref_slice[4], ref_slice[5], ref_slice[6], ref_slice[7]])
335						as usize;
336				Some((offset, length))
337			}
338			ValueType::Int | ValueType::Uint | ValueType::Decimal => {
339				// SAFETY: these three types occupy a 16-byte static slot, and the shape
340				// guarantees field.offset + 16 lies inside the row's static section;
341				// read_unaligned needs no alignment and u128 has no invalid patterns.
342				let packed = unsafe {
343					(row.as_ptr().add(field.offset as usize) as *const u128).read_unaligned()
344				};
345				let packed = u128::from_le(packed);
346				if packed & PACKED_MODE_MASK != 0 {
347					let offset = (packed & PACKED_OFFSET_MASK) as usize;
348					let length = ((packed & PACKED_LENGTH_MASK) >> 64) as usize;
349					Some((offset, length))
350				} else {
351					None
352				}
353			}
354			_ => None,
355		}
356	}
357
358	pub(crate) fn write_dynamic_ref(&self, row: &mut impl RowBuilder, index: usize, offset: usize, length: usize) {
359		let field = &self.fields()[index];
360		match field.constraint.get_type().inner_type() {
361			ValueType::Utf8 | ValueType::Blob | ValueType::Any => {
362				let ref_slice =
363					&mut row.as_mut_slice()[field.offset as usize..field.offset as usize + 8];
364				ref_slice[0..4].copy_from_slice(&(offset as u32).to_le_bytes());
365				ref_slice[4..8].copy_from_slice(&(length as u32).to_le_bytes());
366			}
367			ValueType::Int | ValueType::Uint | ValueType::Decimal => {
368				let offset_part = (offset as u128) & PACKED_OFFSET_MASK;
369				let length_part = ((length as u128) << 64) & PACKED_LENGTH_MASK;
370				let packed = PACKED_MODE_DYNAMIC | offset_part | length_part;
371				// SAFETY: these three types occupy a 16-byte static slot, and the shape
372				// guarantees field.offset + 16 lies inside the row's static section;
373				// make_mut() gives unique ownership and write_unaligned needs no alignment.
374				unsafe {
375					ptr::write_unaligned(
376						row.as_mut_slice().as_mut_ptr().add(field.offset as usize) as *mut u128,
377						packed.to_le(),
378					);
379				}
380			}
381			_ => {}
382		}
383	}
384
385	pub(crate) fn replace_dynamic_data(&self, row: &mut impl RowBuilder, index: usize, new_data: &[u8]) {
386		if let Some((old_offset, old_length)) = self.read_dynamic_ref(row.as_slice(), index) {
387			let delta = new_data.len() as isize - old_length as isize;
388
389			let refs_to_update: Vec<(usize, usize, usize)> = if delta != 0 {
390				self.fields()
391					.iter()
392					.enumerate()
393					.filter(|(i, _)| *i != index && self.is_defined(row.as_slice(), *i))
394					.filter_map(|(i, _)| {
395						self.read_dynamic_ref(row.as_slice(), i)
396							.filter(|(off, _)| *off > old_offset)
397							.map(|(off, len)| (i, off, len))
398					})
399					.collect()
400			} else {
401				vec![]
402			};
403
404			let dynamic_start = self.dynamic_section_start();
405			let abs_start = dynamic_start + old_offset;
406			let abs_end = abs_start + old_length;
407			row.splice(abs_start..abs_end, new_data.iter().copied());
408
409			self.write_dynamic_ref(row, index, old_offset, new_data.len());
410
411			for (i, off, len) in refs_to_update {
412				let new_off = (off as isize + delta) as usize;
413				self.write_dynamic_ref(row, i, new_off, len);
414			}
415		} else {
416			let dynamic_offset = self.dynamic_section_size(row.as_slice());
417			row.extend_from_slice(new_data);
418			self.write_dynamic_ref(row, index, dynamic_offset, new_data.len());
419		}
420		self.set_valid(row, index, true);
421	}
422
423	pub(crate) fn remove_dynamic_data(&self, row: &mut impl RowBuilder, index: usize) {
424		if let Some((old_offset, old_length)) = self.read_dynamic_ref(row.as_slice(), index) {
425			let refs_to_update: Vec<(usize, usize, usize)> = self
426				.fields()
427				.iter()
428				.enumerate()
429				.filter(|(i, _)| *i != index && self.is_defined(row.as_slice(), *i))
430				.filter_map(|(i, _)| {
431					self.read_dynamic_ref(row.as_slice(), i)
432						.filter(|(off, _)| *off > old_offset)
433						.map(|(off, len)| (i, off, len))
434				})
435				.collect();
436
437			let dynamic_start = self.dynamic_section_start();
438			let abs_start = dynamic_start + old_offset;
439			let abs_end = abs_start + old_length;
440			row.splice(abs_start..abs_end, iter::empty());
441
442			for (i, off, len) in refs_to_update {
443				let new_off = off - old_length;
444				self.write_dynamic_ref(row, i, new_off, len);
445			}
446		}
447	}
448
449	fn allocate(&self) -> EncodedRowBuilder {
450		let total_size = self.get_cached_layout();
451		let mut row = EncodedRowBuilder::zeroed(total_size);
452		match self.family {
453			RowFamily::Pod => {}
454			RowFamily::Operator => write_operator_time(row.as_mut_slice(), DateTime::MAX),
455			_ => write_fingerprint(row.as_mut_slice(), self.fingerprint),
456		}
457		reifydb_assertions! {
458			assert!(
459				row.len() == total_size,
460				"allocated row length does not match the shape total_static_size, so any field accessor using pre-computed offsets will read from garbage memory (row_len={} total_size={})",
461				row.len(),
462				total_size
463			);
464		}
465		row
466	}
467
468	pub fn allocate_catalog(&self) -> EncodedCatalogRowBuilder {
469		assert_eq!(self.family, RowFamily::Catalog, "allocate_catalog on a shape of another family");
470		EncodedCatalogRowBuilder::wrap(self.allocate())
471	}
472
473	pub fn allocate_pod(&self) -> EncodedPodRowBuilder {
474		assert_eq!(self.family, RowFamily::Pod, "allocate_pod on a shape of another family");
475		EncodedPodRowBuilder::wrap(self.allocate())
476	}
477
478	pub fn allocate_operator(&self) -> EncodedOperatorRowBuilder {
479		assert_eq!(self.family, RowFamily::Operator, "allocate_operator on a shape of another family");
480		EncodedOperatorRowBuilder::wrap(self.allocate())
481	}
482
483	pub fn allocate_table(&self) -> EncodedTableRowBuilder {
484		assert_eq!(self.family, RowFamily::Table, "allocate_table on a shape of another family");
485		EncodedTableRowBuilder::wrap(self.allocate())
486	}
487
488	pub fn allocate_series(&self) -> EncodedSeriesRowBuilder {
489		assert_eq!(self.family, RowFamily::Series, "allocate_series on a shape of another family");
490		EncodedSeriesRowBuilder::wrap(self.allocate())
491	}
492
493	pub fn allocate_ringbuffer(&self) -> EncodedRingBufferRowBuilder {
494		assert_eq!(self.family, RowFamily::RingBuffer, "allocate_ringbuffer on a shape of another family");
495		EncodedRingBufferRowBuilder::wrap(self.allocate())
496	}
497
498	pub fn allocate_queue(&self) -> EncodedQueueRowBuilder {
499		assert_eq!(self.family, RowFamily::Queue, "allocate_queue on a shape of another family");
500		EncodedQueueRowBuilder::wrap(self.allocate())
501	}
502
503	pub fn allocate_queue_attempt(&self) -> EncodedQueueAttemptRowBuilder {
504		assert_eq!(self.family, RowFamily::QueueAttempt, "allocate_queue_attempt on a shape of another family");
505		EncodedQueueAttemptRowBuilder::wrap(self.allocate())
506	}
507
508	pub fn allocate_queue_deduplication(&self) -> EncodedQueueDeduplicationRowBuilder {
509		assert_eq!(
510			self.family,
511			RowFamily::QueueDeduplication,
512			"allocate_queue_deduplication on a shape of another family"
513		);
514		EncodedQueueDeduplicationRowBuilder::wrap(self.allocate())
515	}
516
517	pub fn set_none(&self, row: &mut impl RowBuilder, index: usize) {
518		self.remove_dynamic_data(row, index);
519		self.set_valid(row, index, false);
520	}
521
522	pub fn testing(family: RowFamily, types: &[ValueType]) -> Self {
523		RowShape::new(
524			family,
525			types.iter()
526				.enumerate()
527				.map(|(i, t)| RowShapeField::unconstrained(format!("f{}", i), t.clone()))
528				.collect(),
529		)
530	}
531}