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