Skip to main content

reifydb_sdk/testing/
builders.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2025 ReifyDB
3
4use reifydb_core::{
5	common::CommitVersion,
6	encoded::shape::{RowShape, RowShapeField},
7	interface::{
8		catalog::{flow::FlowNodeId, id::TableId, shape::ShapeId},
9		change::{Change, ChangeOrigin, Diff, Diffs},
10	},
11	row::Row,
12	value::column::columns::Columns,
13};
14use reifydb_type::value::{Value, datetime::DateTime, row_number::RowNumber, r#type::Type};
15
16pub struct TestRowBuilder {
17	row_number: RowNumber,
18	values: Vec<Value>,
19	shape: Option<RowShape>,
20}
21
22impl TestRowBuilder {
23	pub fn new(row_number: impl Into<RowNumber>) -> Self {
24		Self {
25			row_number: row_number.into(),
26			values: Vec::new(),
27			shape: None,
28		}
29	}
30
31	pub fn with_values(mut self, values: Vec<Value>) -> Self {
32		self.values = values;
33		self
34	}
35
36	pub fn add_value(mut self, value: Value) -> Self {
37		self.values.push(value);
38		self
39	}
40
41	pub fn with_shape(mut self, shape: RowShape) -> Self {
42		self.shape = Some(shape);
43		self
44	}
45
46	pub fn build(self) -> Row {
47		let shape = if let Some(shape) = self.shape {
48			shape
49		} else {
50			let fields: Vec<RowShapeField> = self
51				.values
52				.iter()
53				.enumerate()
54				.map(|(i, v)| RowShapeField::unconstrained(format!("field{}", i), v.get_type()))
55				.collect();
56			RowShape::new(fields)
57		};
58
59		let mut encoded = shape.allocate();
60		shape.set_values(&mut encoded, &self.values);
61
62		Row {
63			number: self.row_number,
64			encoded,
65			shape,
66		}
67	}
68}
69
70pub struct TestChangeBuilder {
71	origin: ChangeOrigin,
72	diffs: Diffs,
73	version: CommitVersion,
74	changed_at: DateTime,
75}
76
77impl Default for TestChangeBuilder {
78	fn default() -> Self {
79		Self::new()
80	}
81}
82
83impl TestChangeBuilder {
84	pub fn new() -> Self {
85		Self {
86			origin: ChangeOrigin::Shape(ShapeId::Table(TableId(1))),
87			diffs: Diffs::new(),
88			version: CommitVersion(1),
89			changed_at: DateTime::default(),
90		}
91	}
92
93	pub fn changed_by_shape(mut self, shape: ShapeId) -> Self {
94		self.origin = ChangeOrigin::Shape(shape);
95		self
96	}
97
98	pub fn changed_by_node(mut self, node: FlowNodeId) -> Self {
99		self.origin = ChangeOrigin::Flow(node);
100		self
101	}
102
103	pub fn with_version(mut self, version: CommitVersion) -> Self {
104		self.version = version;
105		self
106	}
107
108	pub fn with_changed_at(mut self, changed_at: DateTime) -> Self {
109		self.changed_at = changed_at;
110		self
111	}
112
113	pub fn insert(mut self, row: Row) -> Self {
114		self.diffs.push(Diff::insert(Columns::from_row(&row)));
115		self
116	}
117
118	pub fn insert_row(self, row_number: impl Into<RowNumber>, values: Vec<Value>) -> Self {
119		let row = TestRowBuilder::new(row_number).with_values(values).build();
120		self.insert(row)
121	}
122
123	pub fn update(mut self, pre: Row, post: Row) -> Self {
124		self.diffs.push(Diff::update(Columns::from_row(&pre), Columns::from_row(&post)));
125		self
126	}
127
128	pub fn update_row(
129		self,
130		row_number: impl Into<RowNumber>,
131		pre_values: Vec<Value>,
132		post_values: Vec<Value>,
133	) -> Self {
134		let row_number = row_number.into();
135		let pre = TestRowBuilder::new(row_number).with_values(pre_values).build();
136		let post = TestRowBuilder::new(row_number).with_values(post_values).build();
137		self.update(pre, post)
138	}
139
140	pub fn remove(mut self, row: Row) -> Self {
141		self.diffs.push(Diff::remove(Columns::from_row(&row)));
142		self
143	}
144
145	pub fn remove_row(self, row_number: impl Into<RowNumber>, values: Vec<Value>) -> Self {
146		let row = TestRowBuilder::new(row_number).with_values(values).build();
147		self.remove(row)
148	}
149
150	pub fn build(self) -> Change {
151		Change {
152			origin: self.origin,
153			diffs: self.diffs,
154			version: self.version,
155			changed_at: self.changed_at,
156		}
157	}
158}
159
160pub struct TestLayoutBuilder {
161	fields: Vec<RowShapeField>,
162}
163
164impl Default for TestLayoutBuilder {
165	fn default() -> Self {
166		Self::new()
167	}
168}
169
170impl TestLayoutBuilder {
171	pub fn new() -> Self {
172		Self {
173			fields: Vec::new(),
174		}
175	}
176
177	pub fn add_type(mut self, ty: Type) -> Self {
178		let field_name = format!("field{}", self.fields.len());
179		self.fields.push(RowShapeField::unconstrained(field_name, ty));
180		self
181	}
182
183	pub fn add_field(mut self, name: impl Into<String>, ty: Type) -> Self {
184		self.fields.push(RowShapeField::unconstrained(name, ty));
185		self
186	}
187
188	pub fn build(self) -> RowShape {
189		RowShape::new(self.fields)
190	}
191
192	pub fn build_named(self) -> RowShape {
193		self.build()
194	}
195}
196
197pub mod helpers {
198	use reifydb_core::{encoded::shape::RowShape, interface::change::Change, row::Row};
199	use reifydb_type::value::{row_number::RowNumber, r#type::Type};
200
201	use super::*;
202
203	pub fn counter_layout() -> RowShape {
204		TestLayoutBuilder::new().add_type(Type::Int8).build()
205	}
206
207	pub fn key_value_layout() -> RowShape {
208		TestLayoutBuilder::new().add_type(Type::Utf8).add_type(Type::Int8).build()
209	}
210
211	pub fn named_key_value_layout() -> RowShape {
212		TestLayoutBuilder::new().add_field("key", Type::Utf8).add_field("value", Type::Int8).build_named()
213	}
214
215	pub fn int_row(row_number: impl Into<RowNumber>, value: i8) -> Row {
216		TestRowBuilder::new(row_number).with_values(vec![Value::Int8(value as i64)]).build()
217	}
218
219	pub fn key_value_row(row_number: impl Into<RowNumber>, key: &str, value: i8) -> Row {
220		TestRowBuilder::new(row_number)
221			.with_values(vec![Value::Utf8(key.into()), Value::Int8(value as i64)])
222			.build()
223	}
224
225	pub fn empty_change() -> Change {
226		TestChangeBuilder::new().build()
227	}
228
229	pub fn insert_change(row: Row) -> Change {
230		TestChangeBuilder::new().insert(row).build()
231	}
232
233	pub fn batch_insert_change(rows: Vec<Row>) -> Change {
234		let mut builder = TestChangeBuilder::new();
235		for row in rows {
236			builder = builder.insert(row);
237		}
238		builder.build()
239	}
240}
241
242#[cfg(test)]
243pub mod tests {
244	use reifydb_core::{
245		common::CommitVersion,
246		interface::{catalog::shape::ShapeId, change::ChangeOrigin},
247	};
248	use reifydb_type::value::{row_number::RowNumber, r#type::Type};
249
250	use super::{helpers::*, *};
251
252	#[test]
253	fn test_row_builder() {
254		let row = TestRowBuilder::new(42)
255			.add_value(Value::Int8(10i64))
256			.add_value(Value::Utf8("test".into()))
257			.build();
258
259		assert_eq!(row.number, RowNumber(42));
260		assert_eq!(row.shape.field_count(), 2);
261	}
262
263	#[test]
264	fn test_flow_change_builder() {
265		let change = TestChangeBuilder::new()
266			.changed_by_shape(ShapeId::table(100))
267			.with_version(CommitVersion(5))
268			.insert_row(1, vec![Value::Int8(42i64)])
269			.update_row(2, vec![Value::Int8(10i64)], vec![Value::Int8(20i64)])
270			.remove_row(3, vec![Value::Int8(30i64)])
271			.build();
272
273		assert_eq!(change.version, CommitVersion(5));
274		assert_eq!(change.diffs.len(), 3);
275
276		match &change.origin {
277			ChangeOrigin::Shape(shape) => {
278				assert_eq!(*shape, ShapeId::table(100));
279			}
280			_ => panic!("Expected external origin"),
281		}
282	}
283
284	#[test]
285	fn test_layout_builder() {
286		let unnamed = TestLayoutBuilder::new().add_type(Type::Int8).add_type(Type::Utf8).build();
287
288		assert_eq!(unnamed.field_count(), 2);
289
290		let named = TestLayoutBuilder::new()
291			.add_field("count", Type::Int8)
292			.add_field("name", Type::Utf8)
293			.build_named();
294
295		assert_eq!(named.field_count(), 2);
296		assert_eq!(named.get_field_name(0).unwrap(), "count");
297		assert_eq!(named.get_field_name(1).unwrap(), "name");
298	}
299
300	#[test]
301	fn test_helpers() {
302		let row = int_row(1, 42);
303		assert_eq!(row.number, RowNumber(1));
304
305		let kv_row = key_value_row(2, "test", 100);
306		assert_eq!(kv_row.number, RowNumber(2));
307
308		let change = insert_change(row.clone());
309		assert_eq!(change.diffs.len(), 1);
310
311		let batch = batch_insert_change(vec![row.clone(), kv_row.clone()]);
312		assert_eq!(batch.diffs.len(), 2);
313	}
314}