Skip to main content

reifydb_core/
row.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2026 ReifyDB
3
4use reifydb_codec::encoded::{
5	row::EncodedRow,
6	shape::{RowShape, RowShapeField},
7};
8use reifydb_value::value::{
9	constraint::{Constraint, TypeConstraint},
10	duration::Duration,
11	row_number::RowNumber,
12};
13use serde::{Deserialize, Serialize};
14
15use crate::interface::catalog::column::Column;
16
17#[derive(Debug, Clone)]
18pub struct Row {
19	pub number: RowNumber,
20	pub encoded: EncodedRow,
21	pub shape: RowShape,
22}
23
24#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
25pub struct Ttl {
26	pub duration: Duration,
27
28	pub cleanup_mode: TtlCleanupMode,
29}
30
31#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
32pub struct RowSettings {
33	pub ttl: Option<Ttl>,
34
35	pub persistent: bool,
36}
37
38impl RowSettings {
39	pub fn is_persistent(&self) -> bool {
40		self.persistent
41	}
42}
43
44#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
45pub struct OperatorSettings {
46	pub ttl: Option<Ttl>,
47
48	pub join: Option<JoinTtl>,
49}
50
51#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
52pub enum TtlCleanupMode {
53	Delete,
54
55	Drop,
56}
57
58#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
59pub struct JoinTtl {
60	pub left: Option<Ttl>,
61
62	pub right: Option<Ttl>,
63}
64
65pub fn row_shape_from_columns(value: &[Column]) -> RowShape {
66	{
67		let fields = value
68			.iter()
69			.map(|col| {
70				let constraint = match col.constraint.constraint() {
71					Some(Constraint::Dictionary(dict_id, id_type)) => {
72						TypeConstraint::dictionary(*dict_id, id_type.clone())
73					}
74					_ => col.constraint.clone(),
75				};
76				RowShapeField::new(col.name.clone(), constraint)
77			})
78			.collect();
79		RowShape::new(fields)
80	}
81}
82
83#[cfg(test)]
84mod tests {
85	mod from_shape {
86		// Tests removed as From<&RowShape> for the old layout type has been removed
87		// RowShape is now the canonical layout descriptor
88	}
89
90	mod from_column {
91		use reifydb_codec::encoded::shape::{RowShape, RowShapeField};
92		use reifydb_value::value::{constraint::TypeConstraint, value_type::ValueType};
93
94		use crate::{
95			interface::catalog::{
96				column::{Column, ColumnIndex},
97				id::ColumnId,
98			},
99			row::row_shape_from_columns,
100		};
101
102		fn make_column(id: u64, name: &str, ty: ValueType, index: u8) -> Column {
103			Column {
104				id: ColumnId(id),
105				name: name.to_string(),
106				constraint: TypeConstraint::unconstrained(ty),
107				properties: vec![],
108				index: ColumnIndex(index),
109				auto_increment: false,
110				dictionary_id: None,
111			}
112		}
113
114		#[test]
115		fn test_from_column_single_field() {
116			let columns = vec![make_column(1, "id", ValueType::Int8, 0)];
117
118			let shape = row_shape_from_columns(columns.as_slice());
119
120			assert_eq!(shape.fields().len(), 1);
121			assert_eq!(shape.fields()[0].name, "id");
122			assert_eq!(shape.fields()[0].constraint.get_type(), ValueType::Int8);
123		}
124
125		#[test]
126		fn test_from_column_multiple_fields() {
127			let columns = vec![
128				make_column(1, "a", ValueType::Int1, 0),
129				make_column(2, "b", ValueType::Int2, 1),
130				make_column(3, "c", ValueType::Int4, 2),
131			];
132
133			let shape = row_shape_from_columns(columns.as_slice());
134
135			assert_eq!(shape.fields().len(), 3);
136			assert_eq!(shape.fields()[0].name, "a");
137			assert_eq!(shape.fields()[0].constraint.get_type(), ValueType::Int1);
138			assert_eq!(shape.fields()[1].name, "b");
139			assert_eq!(shape.fields()[1].constraint.get_type(), ValueType::Int2);
140			assert_eq!(shape.fields()[2].name, "c");
141			assert_eq!(shape.fields()[2].constraint.get_type(), ValueType::Int4);
142		}
143
144		#[test]
145		fn test_from_column_preserves_field_order() {
146			let columns = vec![
147				make_column(1, "first", ValueType::Utf8, 0),
148				make_column(2, "second", ValueType::Int4, 1),
149				make_column(3, "third", ValueType::Boolean, 2),
150			];
151
152			let shape = row_shape_from_columns(columns.as_slice());
153
154			assert_eq!(shape.fields()[0].name, "first");
155			assert_eq!(shape.fields()[0].constraint.get_type(), ValueType::Utf8);
156			assert_eq!(shape.fields()[1].name, "second");
157			assert_eq!(shape.fields()[1].constraint.get_type(), ValueType::Int4);
158			assert_eq!(shape.fields()[2].name, "third");
159			assert_eq!(shape.fields()[2].constraint.get_type(), ValueType::Boolean);
160		}
161
162		#[test]
163		fn test_from_column_equivalence_with_direct_construction() {
164			let columns = vec![
165				make_column(1, "f0", ValueType::Uint1, 0),
166				make_column(2, "f1", ValueType::Uint2, 1),
167				make_column(3, "f2", ValueType::Uint4, 2),
168				make_column(4, "f3", ValueType::Uint8, 3),
169				make_column(5, "f4", ValueType::Uint16, 4),
170			];
171
172			let shape_from_columns = row_shape_from_columns(columns.as_slice());
173			let shape_direct = RowShape::new(vec![
174				RowShapeField::unconstrained("f0", ValueType::Uint1),
175				RowShapeField::unconstrained("f1", ValueType::Uint2),
176				RowShapeField::unconstrained("f2", ValueType::Uint4),
177				RowShapeField::unconstrained("f3", ValueType::Uint8),
178				RowShapeField::unconstrained("f4", ValueType::Uint16),
179			]);
180
181			// Full equivalence check
182			assert_eq!(shape_from_columns.fields().len(), shape_direct.fields().len());
183			assert_eq!(shape_from_columns.fingerprint(), shape_direct.fingerprint());
184
185			for (i, (from_columns, direct)) in
186				shape_from_columns.fields().iter().zip(shape_direct.fields().iter()).enumerate()
187			{
188				assert_eq!(from_columns.name, direct.name, "name mismatch at field {}", i);
189				assert_eq!(
190					from_columns.constraint, direct.constraint,
191					"constraint mismatch at field {}",
192					i
193				);
194				assert_eq!(from_columns.offset, direct.offset, "offset mismatch at field {}", i);
195				assert_eq!(from_columns.size, direct.size, "size mismatch at field {}", i);
196				assert_eq!(from_columns.align, direct.align, "align mismatch at field {}", i);
197			}
198		}
199
200		#[test]
201		fn test_from_column_empty() {
202			let columns: Vec<Column> = vec![];
203
204			let shape = row_shape_from_columns(columns.as_slice());
205
206			assert_eq!(shape.fields().len(), 0);
207		}
208
209		#[test]
210		fn test_from_column_nine_fields() {
211			let columns = vec![
212				make_column(1, "f0", ValueType::Boolean, 0),
213				make_column(2, "f1", ValueType::Int1, 1),
214				make_column(3, "f2", ValueType::Int2, 2),
215				make_column(4, "f3", ValueType::Int4, 3),
216				make_column(5, "f4", ValueType::Int8, 4),
217				make_column(6, "f5", ValueType::Uint1, 5),
218				make_column(7, "f6", ValueType::Uint2, 6),
219				make_column(8, "f7", ValueType::Uint4, 7),
220				make_column(9, "f8", ValueType::Uint8, 8),
221			];
222
223			let shape = row_shape_from_columns(columns.as_slice());
224
225			assert_eq!(shape.fields().len(), 9);
226			for (i, field) in shape.fields().iter().enumerate() {
227				assert_eq!(field.name, format!("f{}", i));
228			}
229		}
230	}
231}