Skip to main content

reifydb_core/
row.rs

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