Skip to main content

reifydb_codec/row/shape/
mod.rs

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