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