Skip to main content

reifydb_macro_impl/
key_conformance.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2026 ReifyDB
3
4use crate::key::{KeyColumn, KeyField};
5
6#[derive(Clone, Copy, PartialEq)]
7pub enum Direction {
8	Asc,
9	Desc,
10}
11
12pub struct OrderComponent {
13	projection: &'static str,
14	direction: Direction,
15}
16
17impl KeyColumn {
18	pub fn samples(self, ty: &str) -> String {
19		match self {
20			KeyColumn::U8 => "vec![0u8, 1u8, u8::MAX - 1, u8::MAX]".to_string(),
21			KeyColumn::U16 => "vec![0u16, 1u16, u16::MAX - 1, u16::MAX]".to_string(),
22			KeyColumn::U32 => "vec![0u32, 1u32, u32::MAX - 1, u32::MAX]".to_string(),
23			KeyColumn::U64 => "vec![0u64, 1u64, u64::MAX - 1, u64::MAX]".to_string(),
24			KeyColumn::U128 => "vec![0u128, 1u128, u128::MAX - 1, u128::MAX]".to_string(),
25			KeyColumn::ReprU8 => {
26				format!("{{ let mut __v = Vec::new(); for __b in 0u8..=u8::MAX {{ if let Ok(__x) = \
27				 {ty}::try_from(__b) {{ __v.push(__x); }} }} __v }}")
28			}
29			KeyColumn::RowNumber => {
30				"vec![RowNumber(0), RowNumber(1), RowNumber(u64::MAX - 1), RowNumber(u64::MAX)]"
31					.to_string()
32			}
33			KeyColumn::GroupId => "vec![GroupId::from_bytes([0u8; 24]), GroupId::from_bytes([1u8; 24]), \
34				GroupId::from_bytes([254u8; 24]), GroupId::from_bytes([255u8; 24])]"
35				.to_string(),
36			KeyColumn::Blob16 => "vec![[0u8; 16], [1u8; 16], [254u8; 16], [255u8; 16]]".to_string(),
37			KeyColumn::IdentityId => "vec![IdentityId::default(), IdentityId::anonymous(), \
38				IdentityId::system(), IdentityId::root()]"
39				.to_string(),
40			KeyColumn::ObjectId => "{ let mut __v = Vec::new(); for __tag in 0u8..=u8::MAX { for __id in \
41				[0u64, 1u64, u64::MAX - 1, u64::MAX] { if let Some(__o) = \
42				ObjectId::from_type_tag(__tag, __id) { __v.push(__o); } } } __v }"
43				.to_string(),
44			KeyColumn::StorageId => "{ let mut __v = Vec::new(); for __tag in 0u8..=u8::MAX { for __id in \
45				[0u64, 1u64, u64::MAX - 1, u64::MAX] { if let Some(__o) = \
46				StorageId::from_type_tag(__tag, __id) { __v.push(__o); } } } __v }"
47				.to_string(),
48			KeyColumn::ProcedureId => "vec![ProcedureId::from_raw(0), ProcedureId::from_raw(1), \
49				ProcedureId::from_raw(u64::MAX - 1), ProcedureId::from_raw(u64::MAX)]"
50				.to_string(),
51			KeyColumn::EpochSeconds => "vec![EpochSeconds::new(0), EpochSeconds::new(1), \
52				EpochSeconds::new(u64::MAX - 1), EpochSeconds::new(u64::MAX)]"
53				.to_string(),
54			KeyColumn::DateTime => "vec![DateTime::from_nanos(0), DateTime::from_nanos(1), \
55				DateTime::from_nanos(u64::MAX - 1), DateTime::from_nanos(u64::MAX)]"
56				.to_string(),
57			KeyColumn::RowShapeFingerprint => {
58				"vec![RowShapeFingerprint::new(0), RowShapeFingerprint::new(1), \
59				 RowShapeFingerprint::new(u64::MAX - 1), RowShapeFingerprint::new(u64::MAX)]"
60					.to_string()
61			}
62			KeyColumn::Partition => {
63				"vec![Partition(0), Partition(1), Partition(u128::MAX - 1), Partition(u128::MAX)]"
64					.to_string()
65			}
66			KeyColumn::IndexId => "vec![IndexId::primary(0u64), IndexId::primary(1u64), \
67				IndexId::primary(u64::MAX - 1), IndexId::primary(u64::MAX)]"
68				.to_string(),
69			KeyColumn::OptionU8 => "vec![None, Some(0u8), Some(1u8), Some(254u8), Some(255u8)]".to_string(),
70			KeyColumn::TableId
71			| KeyColumn::ColumnId
72			| KeyColumn::FlowId
73			| KeyColumn::FlowEdgeId
74			| KeyColumn::OperatorId
75			| KeyColumn::HandlerId
76			| KeyColumn::NamespaceId
77			| KeyColumn::SumTypeId
78			| KeyColumn::SequenceId
79			| KeyColumn::ViewId
80			| KeyColumn::SeriesId
81			| KeyColumn::SinkId
82			| KeyColumn::SourceId
83			| KeyColumn::QueueId
84			| KeyColumn::RingBufferId
85			| KeyColumn::BindingId
86			| KeyColumn::DictionaryId
87			| KeyColumn::ColumnPropertyId
88			| KeyColumn::RelationshipId
89			| KeyColumn::MigrationId
90			| KeyColumn::MigrationEventId
91			| KeyColumn::ColumnSnapshotId
92			| KeyColumn::PrimaryKeyId => {
93				format!("vec![{ty}(0), {ty}(1), {ty}(u64::MAX - 1), {ty}(u64::MAX)]")
94			}
95		}
96	}
97
98	pub fn order(self) -> Vec<OrderComponent> {
99		let desc = |projection| OrderComponent {
100			projection,
101			direction: Direction::Desc,
102		};
103		let asc = |projection| OrderComponent {
104			projection,
105			direction: Direction::Asc,
106		};
107		match self {
108			KeyColumn::U8 | KeyColumn::U16 | KeyColumn::U32 | KeyColumn::U64 | KeyColumn::U128 => {
109				vec![desc("$")]
110			}
111			KeyColumn::ReprU8 => vec![desc("($ as u8)")],
112			KeyColumn::GroupId | KeyColumn::IdentityId => vec![desc("(*$.as_bytes())")],
113			KeyColumn::Blob16 => vec![asc("$")],
114			KeyColumn::ObjectId | KeyColumn::StorageId => {
115				vec![asc("$.type_tag()"), desc("$.as_u64()")]
116			}
117			KeyColumn::OptionU8 => vec![desc("$.is_some()"), desc("$.unwrap_or(0u8)")],
118			KeyColumn::ProcedureId => vec![desc("(*$)")],
119			KeyColumn::EpochSeconds => vec![desc("$.seconds()")],
120			KeyColumn::DateTime => vec![desc("$.to_nanos()")],
121			KeyColumn::RowShapeFingerprint | KeyColumn::IndexId => vec![desc("$.as_u64()")],
122			KeyColumn::RowNumber | KeyColumn::Partition => vec![desc("$.0")],
123			KeyColumn::TableId
124			| KeyColumn::ColumnId
125			| KeyColumn::FlowId
126			| KeyColumn::FlowEdgeId
127			| KeyColumn::OperatorId
128			| KeyColumn::HandlerId
129			| KeyColumn::NamespaceId
130			| KeyColumn::SumTypeId
131			| KeyColumn::SequenceId
132			| KeyColumn::ViewId
133			| KeyColumn::SeriesId
134			| KeyColumn::SinkId
135			| KeyColumn::SourceId
136			| KeyColumn::QueueId
137			| KeyColumn::RingBufferId
138			| KeyColumn::BindingId
139			| KeyColumn::DictionaryId
140			| KeyColumn::ColumnPropertyId
141			| KeyColumn::RelationshipId
142			| KeyColumn::MigrationId
143			| KeyColumn::MigrationEventId
144			| KeyColumn::ColumnSnapshotId
145			| KeyColumn::PrimaryKeyId => vec![desc("$.0")],
146		}
147	}
148}
149
150pub fn expand_tests(name: &str, fields: &[KeyField]) -> String {
151	let mut out = String::new();
152
153	out.push_str("#[cfg(test)]\n#[allow(non_snake_case, unused_imports, unused_variables)]\n");
154	out.push_str(&format!("mod __key_conformance_{name} {{\n"));
155	out.push_str("\tuse std::cmp::Ordering;\n\n");
156	out.push_str("\tuse super::*;\n\n");
157
158	out.push_str(&samples_fn(name, fields));
159	out.push('\n');
160	out.push_str(&expected_order_fn(name, fields));
161	out.push('\n');
162	out.push_str(&round_trip_test(name));
163	out.push('\n');
164	out.push_str(&order_test(name));
165	out.push('\n');
166	out.push_str(&byte_replay_test(name));
167
168	out.push_str("}\n");
169	out
170}
171
172fn samples_fn(name: &str, fields: &[KeyField]) -> String {
173	let mut out = String::new();
174	out.push_str(&format!("\tfn __samples() -> Vec<{name}> {{\n"));
175
176	for field in fields {
177		out.push_str(&format!(
178			"\t\tlet __f_{}: Vec<{}> = {};\n\t\tassert!(!__f_{}.is_empty(), \"column '{}' of \
179			 {name} produced no boundary values to compare\");\n",
180			field.name,
181			field.ty,
182			field.column.samples(&field.ty),
183			field.name,
184			field.name
185		));
186	}
187
188	out.push_str(&format!("\t\tlet mut __out: Vec<{name}> = Vec::new();\n"));
189
190	if fields.is_empty() {
191		out.push_str(&format!("\t\t__out.push({name} {{}});\n"));
192	} else {
193		for (index, field) in fields.iter().enumerate() {
194			let skip = if index == 0 {
195				""
196			} else {
197				".skip(1)"
198			};
199			out.push_str(&format!("\t\tfor __v in __f_{}.iter(){skip} {{\n", field.name));
200			out.push_str(&format!("\t\t\t__out.push({name} {{\n"));
201			for other in fields {
202				if other.name == field.name {
203					out.push_str(&format!("\t\t\t\t{}: __v.clone(),\n", other.name));
204				} else {
205					out.push_str(&format!(
206						"\t\t\t\t{}: __f_{}[0].clone(),\n",
207						other.name, other.name
208					));
209				}
210			}
211			out.push_str("\t\t\t});\n\t\t}\n");
212		}
213	}
214
215	out.push_str("\t\t__out\n\t}\n");
216	out
217}
218
219fn expected_order_fn(name: &str, fields: &[KeyField]) -> String {
220	let mut out = String::new();
221	out.push_str(&format!("\tfn __expected_order(__a: &{name}, __b: &{name}) -> (Ordering, &'static str) {{\n"));
222
223	for field in fields {
224		for component in field.column.order() {
225			let left = component.projection.replace('$', &format!("__a.{}", field.name));
226			let right = component.projection.replace('$', &format!("__b.{}", field.name));
227			let compared = match component.direction {
228				Direction::Asc => format!("{left}.cmp(&{right})"),
229				Direction::Desc => format!("{right}.cmp(&{left})"),
230			};
231			out.push_str(&format!("\t\tlet __ord = {compared};\n"));
232			out.push_str(&format!(
233				"\t\tif __ord != Ordering::Equal {{\n\t\t\treturn (__ord, \"{}\");\n\t\t}}\n",
234				field.name
235			));
236		}
237	}
238
239	out.push_str("\t\t(Ordering::Equal, \"<no column>\")\n\t}\n");
240	out
241}
242
243fn round_trip_test(name: &str) -> String {
244	let mut out = String::new();
245	out.push_str("\t// a key that cannot be read back has silently lost a column somewhere in its bytes.\n");
246	out.push_str("\t#[test]\n\tfn every_boundary_instance_decodes_back_to_itself() {\n");
247	out.push_str("\t\tfor __key in __samples().iter() {\n");
248	out.push_str(&format!("\t\t\tlet __encoded = {name}::encode(__key);\n"));
249	out.push_str(&format!("\t\t\tlet __decoded = {name}::decode(&__encoded);\n"));
250	out.push_str(&format!(
251		"\t\t\tassert_eq!(\n\t\t\t\t__decoded.as_ref(),\n\t\t\t\tSome(__key),\n\t\t\t\t\"{name} did not \
252		 survive a round trip: {{:?}} encoded to {{:?}}\",\n\t\t\t\t__key,\n\t\t\t\t\
253		 __encoded.as_slice()\n\t\t\t);\n"
254	));
255	out.push_str("\t\t}\n\t}\n");
256	out
257}
258
259fn byte_replay_test(name: &str) -> String {
260	let mut out = String::new();
261	out.push_str(
262		"\t// a projection that sorts right can still write the wrong bytes: fixed and escaped byte\n\t		 // encodings sort alike, and every integer width compares the same way. Only replaying the\n\t		 // bytes catches an encoding tag that does not match the column's serializer call.\n",
263	);
264	out.push_str("\t#[test]\n\tfn every_boundary_instance_replays_its_encoded_bytes_through_fields() {\n");
265	out.push_str("\t\tfor __key in __samples().iter() {\n");
266	out.push_str(&format!("\t\t\tlet mut __replayed = vec![!({name}::TAG as u8)];\n"));
267	out.push_str("\t\t\tfor __field in __key.fields().iter() {\n");
268	out.push_str("\t\t\t\t__field.encode(&mut __replayed);\n\t\t\t}\n");
269	out.push_str(&format!(
270		"\t\t\tassert_eq!(\n\t\t\t\t__replayed.as_slice(),\n\t\t\t\t{name}::encode(__key).as_slice(),\n\t\t\t\t\"{name}: fields() does not replay the bytes its 		 encoder wrote for {{:?}}\",\n\t\t\t\t__key\n\t\t\t);\n"
271	));
272	out.push_str("\t\t}\n\t}\n");
273	out
274}
275
276fn order_test(name: &str) -> String {
277	let mut out = String::new();
278	out.push_str("\t// typed keys will replace byte keys in program logic, so both must sort alike.\n");
279	out.push_str("\t#[test]\n\tfn encoded_byte_order_matches_the_declared_column_directions() {\n");
280	out.push_str("\t\tlet __samples = __samples();\n");
281	out.push_str(&format!("\t\tassert!(!__samples.is_empty(), \"{name} produced no instances to compare\");\n"));
282	out.push_str("\t\tfor __a in __samples.iter() {\n\t\t\tfor __b in __samples.iter() {\n");
283	out.push_str("\t\t\t\tlet (__expected, __column) = __expected_order(__a, __b);\n");
284	out.push_str(&format!("\t\t\t\tlet __ea = {name}::encode(__a);\n"));
285	out.push_str(&format!("\t\t\t\tlet __eb = {name}::encode(__b);\n"));
286	out.push_str("\t\t\t\tlet __actual = __ea.as_slice().cmp(__eb.as_slice());\n");
287	out.push_str(
288		"\t\t\t\tassert_eq!(\n\t\t\t\t\t__expected,\n\t\t\t\t\t__expected_order(__b, \
289		 __a).0.reverse(),\n\t\t\t\t\t\"the declared direction of column '{}' is not antisymmetric, so it \
290		 cannot be a total order\",\n\t\t\t\t\t__column\n\t\t\t\t);\n",
291	);
292	out.push_str(&format!(
293		"\t\t\t\tassert_eq!(\n\t\t\t\t\t__actual,\n\t\t\t\t\t__expected,\n\t\t\t\t\t\"{name}: encoded byte \
294		 order disagrees with the declared direction of column '{{}}'\\n  a = {{:?}}\\n  b = {{:?}}\\n  a \
295		 bytes = {{:?}}\\n  b bytes = {{:?}}\",\n\t\t\t\t\t__column,\n\t\t\t\t\t__a,\n\t\t\t\t\t\
296		 __b,\n\t\t\t\t\t__ea.as_slice(),\n\t\t\t\t\t__eb.as_slice()\n\t\t\t\t);\n"
297	));
298	out.push_str(&format!("\t\t\t\tassert_eq!(\n\t\t\t\t\t__actual == Ordering::Equal,\n\t\t\t\t\t__a == \
299		 __b,\n\t\t\t\t\t\"{name}: two distinct keys share an encoding, or one key encodes two ways\\n  a \
300		 = {{:?}}\\n  b = {{:?}}\\n  a bytes = {{:?}}\\n  b bytes = \
301		 {{:?}}\",\n\t\t\t\t\t__a,\n\t\t\t\t\t__b,\n\t\t\t\t\t__ea.as_slice(),\n\t\t\t\t\t\
302		 __eb.as_slice()\n\t\t\t\t);\n"));
303	out.push_str(&format!("\t\t\t\tassert_eq!(\n\t\t\t\t\t__a.fields().cmp(&__b.fields()),\n\t\t\t\t\t\
304		 __actual,\n\t\t\t\t\t\"{name}: fields() order disagrees with encoded byte order\\n  a = \
305		 {{:?}}\\n  b = {{:?}}\\n  a bytes = {{:?}}\\n  b bytes = \
306		 {{:?}}\",\n\t\t\t\t\t__a,\n\t\t\t\t\t__b,\n\t\t\t\t\t__ea.as_slice(),\n\t\t\t\t\t\
307		 __eb.as_slice()\n\t\t\t\t);\n"));
308	out.push_str("\t\t\t}\n\t\t}\n\t}\n");
309	out
310}