1use 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::MIN, DateTime::from_nanos(-1), DateTime::EPOCH, \
55 DateTime::from_nanos(1), DateTime::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::Str => format!(
71 "vec![{ty}::new(\"\"), {ty}::new(\"a\"), {ty}::new(\"a\\u{{0}}b\"), {ty}::new(\"b\")]"
72 ),
73 KeyColumn::DisplayStr => format!("{ty}::all().to_vec()"),
74 KeyColumn::EscapedBytes | KeyColumn::SortRun => format!(
75 "vec![{ty}::new([0u8; 0]), {ty}::new([0u8]), {ty}::new([0u8, 255u8]), {ty}::new([97u8]), {ty}::new([255u8])]"
76 ),
77 KeyColumn::KeyspaceId => {
78 "vec![KeyspaceId(0), KeyspaceId(1), KeyspaceId(254), KeyspaceId(255)]".to_string()
79 }
80 KeyColumn::RawTail if ty == "Vec<u8>" => {
81 "vec![Vec::new(), vec![0u8], vec![0u8, 1u8], vec![255u8]]".to_string()
82 }
83 KeyColumn::RawTail => {
84 format!(
85 "vec![{ty}::new([0u8; 0]), {ty}::new([0u8]), {ty}::new([0u8, 1u8]), {ty}::new([255u8])]"
86 )
87 }
88 KeyColumn::TaggedIndexId => "vec![IndexId::primary(0u64), IndexId::primary(1u64), \
89 IndexId::primary(u64::MAX - 1), IndexId::primary(u64::MAX)]"
90 .to_string(),
91 KeyColumn::VarintU128 => {
92 "vec![0u128, 1u128, 127u128, 128u128, u64::MAX as u128, u128::MAX]".to_string()
93 }
94 KeyColumn::AscRowNumber => {
95 "vec![Asc(RowNumber(0)), Asc(RowNumber(1)), Asc(RowNumber(u64::MAX - 1)), \
96 Asc(RowNumber(u64::MAX))]"
97 .to_string()
98 }
99 KeyColumn::AscU64 => "vec![Asc(0u64), Asc(1u64), Asc(u64::MAX - 1), Asc(u64::MAX)]".to_string(),
100 KeyColumn::TableId
101 | KeyColumn::ColumnId
102 | KeyColumn::FlowId
103 | KeyColumn::FlowEdgeId
104 | KeyColumn::OperatorId
105 | KeyColumn::HandlerId
106 | KeyColumn::NamespaceId
107 | KeyColumn::SumTypeId
108 | KeyColumn::SequenceId
109 | KeyColumn::ViewId
110 | KeyColumn::SeriesId
111 | KeyColumn::SinkId
112 | KeyColumn::WindowedViewGroupId
113 | KeyColumn::SourceId
114 | KeyColumn::QueueId
115 | KeyColumn::RingBufferId
116 | KeyColumn::BindingId
117 | KeyColumn::DictionaryId
118 | KeyColumn::ColumnPropertyId
119 | KeyColumn::RelationshipId
120 | KeyColumn::MigrationId
121 | KeyColumn::MigrationEventId
122 | KeyColumn::ColumnBlockId
123 | KeyColumn::PrimaryKeyId => {
124 format!("vec![{ty}(0), {ty}(1), {ty}(u64::MAX - 1), {ty}(u64::MAX)]")
125 }
126 }
127 }
128
129 pub fn order(self) -> Vec<OrderComponent> {
130 let desc = |projection| OrderComponent {
131 projection,
132 direction: Direction::Desc,
133 };
134 let asc = |projection| OrderComponent {
135 projection,
136 direction: Direction::Asc,
137 };
138 match self {
139 KeyColumn::U8 | KeyColumn::U16 | KeyColumn::U32 | KeyColumn::U64 | KeyColumn::U128 => {
140 vec![desc("$")]
141 }
142 KeyColumn::ReprU8 => vec![desc("($ as u8)")],
143 KeyColumn::GroupId | KeyColumn::IdentityId => vec![desc("(*$.as_bytes())")],
144 KeyColumn::Blob16 => vec![asc("$")],
145 KeyColumn::ObjectId | KeyColumn::StorageId => {
146 vec![asc("$.type_tag()"), desc("$.as_u64()")]
147 }
148 KeyColumn::OptionU8 => vec![desc("$.is_some()"), desc("$.unwrap_or(0u8)")],
149 KeyColumn::Str => vec![desc("$.as_ref().as_bytes()")],
150 KeyColumn::DisplayStr => vec![desc("$.to_string().into_bytes()")],
151 KeyColumn::EscapedBytes => vec![desc("$.as_slice()")],
152 KeyColumn::KeyspaceId => vec![desc("$.0")],
153 KeyColumn::RawTail | KeyColumn::SortRun => vec![asc("$.as_slice()")],
154 KeyColumn::TaggedIndexId => vec![asc("index_tag(&$)"), desc("$.as_u64()")],
155 KeyColumn::VarintU128 => vec![desc("$")],
156 KeyColumn::AscRowNumber => vec![asc("$.0.0")],
157 KeyColumn::AscU64 => vec![asc("$.0")],
158 KeyColumn::ProcedureId => vec![desc("(*$)")],
159 KeyColumn::EpochSeconds => vec![desc("$.seconds()")],
160 KeyColumn::DateTime => vec![desc("$.to_nanos()")],
161 KeyColumn::RowShapeFingerprint | KeyColumn::IndexId => vec![desc("$.as_u64()")],
162 KeyColumn::RowNumber | KeyColumn::Partition => vec![desc("$.0")],
163 KeyColumn::TableId
164 | KeyColumn::ColumnId
165 | KeyColumn::FlowId
166 | KeyColumn::FlowEdgeId
167 | KeyColumn::OperatorId
168 | KeyColumn::HandlerId
169 | KeyColumn::NamespaceId
170 | KeyColumn::SumTypeId
171 | KeyColumn::SequenceId
172 | KeyColumn::ViewId
173 | KeyColumn::SeriesId
174 | KeyColumn::SinkId
175 | KeyColumn::WindowedViewGroupId
176 | KeyColumn::SourceId
177 | KeyColumn::QueueId
178 | KeyColumn::RingBufferId
179 | KeyColumn::BindingId
180 | KeyColumn::DictionaryId
181 | KeyColumn::ColumnPropertyId
182 | KeyColumn::RelationshipId
183 | KeyColumn::MigrationId
184 | KeyColumn::MigrationEventId
185 | KeyColumn::ColumnBlockId
186 | KeyColumn::PrimaryKeyId => vec![desc("$.0")],
187 }
188 }
189}
190
191pub fn expand_tests(name: &str, fields: &[KeyField]) -> String {
192 let mut out = String::new();
193
194 out.push_str("#[cfg(test)]\n#[allow(non_snake_case, unused_imports, unused_variables)]\n");
195 out.push_str(&format!("mod __key_conformance_{name} {{\n"));
196 out.push_str("\tuse std::cmp::Ordering;\n\n");
197 out.push_str("\tuse super::*;\n\n");
198
199 out.push_str(&samples_fn(name, fields));
200 out.push('\n');
201 out.push_str(&expected_order_fn(name, fields));
202 out.push('\n');
203 out.push_str(&round_trip_test(name));
204 out.push('\n');
205 out.push_str(&order_test(name));
206 out.push('\n');
207 out.push_str(&byte_replay_test(name));
208 out.push('\n');
209 out.push_str(&cmp_fields_test(name));
210
211 out.push_str("}\n");
212 out
213}
214
215fn samples_fn(name: &str, fields: &[KeyField]) -> String {
216 let mut out = String::new();
217 out.push_str(&format!("\tfn __samples() -> Vec<{name}> {{\n"));
218
219 for field in fields {
220 out.push_str(&format!(
221 "\t\tlet __f_{}: Vec<{}> = {};\n\t\tassert!(!__f_{}.is_empty(), \"column '{}' of \
222 {name} produced no boundary values to compare\");\n",
223 field.name,
224 field.ty,
225 field.column.samples(&field.ty),
226 field.name,
227 field.name
228 ));
229 }
230
231 out.push_str(&format!("\t\tlet mut __out: Vec<{name}> = Vec::new();\n"));
232
233 if fields.is_empty() {
234 out.push_str(&format!("\t\t__out.push({name} {{}});\n"));
235 } else {
236 for (index, field) in fields.iter().enumerate() {
237 let skip = if index == 0 {
238 ""
239 } else {
240 ".skip(1)"
241 };
242 out.push_str(&format!("\t\tfor __v in __f_{}.iter(){skip} {{\n", field.name));
243 out.push_str(&format!("\t\t\t__out.push({name} {{\n"));
244 for other in fields {
245 if other.name == field.name {
246 out.push_str(&format!("\t\t\t\t{}: __v.clone(),\n", other.name));
247 } else {
248 out.push_str(&format!(
249 "\t\t\t\t{}: __f_{}[0].clone(),\n",
250 other.name, other.name
251 ));
252 }
253 }
254 out.push_str("\t\t\t});\n\t\t}\n");
255 }
256 }
257
258 out.push_str("\t\t__out\n\t}\n");
259 out
260}
261
262fn expected_order_fn(name: &str, fields: &[KeyField]) -> String {
263 let mut out = String::new();
264 out.push_str(&format!("\tfn __expected_order(__a: &{name}, __b: &{name}) -> (Ordering, &'static str) {{\n"));
265
266 for field in fields {
267 for component in field.column.order() {
268 let left = component.projection.replace('$', &format!("__a.{}", field.name));
269 let right = component.projection.replace('$', &format!("__b.{}", field.name));
270 let compared = match component.direction {
271 Direction::Asc => format!("{left}.cmp(&{right})"),
272 Direction::Desc => format!("{right}.cmp(&{left})"),
273 };
274 out.push_str(&format!("\t\tlet __ord = {compared};\n"));
275 out.push_str(&format!(
276 "\t\tif __ord != Ordering::Equal {{\n\t\t\treturn (__ord, \"{}\");\n\t\t}}\n",
277 field.name
278 ));
279 }
280 }
281
282 out.push_str("\t\t(Ordering::Equal, \"<no column>\")\n\t}\n");
283 out
284}
285
286fn cmp_fields_test(name: &str) -> String {
287 let mut out = String::new();
288 out.push_str(
289 "\t// the direct comparison must agree with the field list it shortcuts, or typed sorts drift from it.\n",
290 );
291 out.push_str("\t#[test]\n\tfn direct_field_comparison_agrees_with_the_field_list() {\n");
292 out.push_str("\t\tlet __samples = __samples();\n");
293 out.push_str("\t\tfor __a in __samples.iter() {\n\t\t\tfor __b in __samples.iter() {\n");
294 out.push_str(&format!("\t\t\t\tassert_eq!(\n\t\t\t\t\t{name}::cmp_fields(__a, __b),\n\t\t\t\t\t\
295 KeyFields::fields(__a).cmp(&KeyFields::fields(__b)),\n\t\t\t\t\t\"{name} compares {{:?}} and \
296 {{:?}} differently from its field list\",\n\t\t\t\t\t__a,\n\t\t\t\t\t__b\n\t\t\t\t);\n"));
297 out.push_str("\t\t\t}\n\t\t}\n\t}\n");
298 out
299}
300
301fn round_trip_test(name: &str) -> String {
302 let mut out = String::new();
303 out.push_str("\t// a key that cannot be read back has silently lost a column somewhere in its bytes.\n");
304 out.push_str("\t#[test]\n\tfn every_boundary_instance_decodes_back_to_itself() {\n");
305 out.push_str("\t\tfor __key in __samples().iter() {\n");
306 out.push_str(&format!("\t\t\tlet __encoded = {name}::encode(__key);\n"));
307 out.push_str(&format!("\t\t\tlet __decoded = {name}::decode(&__encoded);\n"));
308 out.push_str(&format!(
309 "\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 \
310 survive a round trip: {{:?}} encoded to {{:?}}\",\n\t\t\t\t__key,\n\t\t\t\t\
311 __encoded.as_slice()\n\t\t\t);\n"
312 ));
313 out.push_str("\t\t}\n\t}\n");
314 out
315}
316
317fn byte_replay_test(name: &str) -> String {
318 let mut out = String::new();
319 out.push_str(
320 "\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",
321 );
322 out.push_str("\t#[test]\n\tfn every_boundary_instance_replays_its_encoded_bytes_through_fields() {\n");
323 out.push_str("\t\tfor __key in __samples().iter() {\n");
324 out.push_str(&format!("\t\t\tlet mut __replayed = vec![!({name}::TAG as u8)];\n"));
325 out.push_str("\t\t\tfor __field in __key.fields().iter() {\n");
326 out.push_str("\t\t\t\t__field.encode(&mut __replayed);\n\t\t\t}\n");
327 out.push_str(&format!(
328 "\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"
329 ));
330 out.push_str("\t\t}\n\t}\n");
331 out
332}
333
334fn order_test(name: &str) -> String {
335 let mut out = String::new();
336 out.push_str("\t// typed keys will replace byte keys in program logic, so both must sort alike.\n");
337 out.push_str("\t#[test]\n\tfn encoded_byte_order_matches_the_declared_column_directions() {\n");
338 out.push_str("\t\tlet __samples = __samples();\n");
339 out.push_str(&format!("\t\tassert!(!__samples.is_empty(), \"{name} produced no instances to compare\");\n"));
340 out.push_str("\t\tfor __a in __samples.iter() {\n\t\t\tfor __b in __samples.iter() {\n");
341 out.push_str("\t\t\t\tlet (__expected, __column) = __expected_order(__a, __b);\n");
342 out.push_str(&format!("\t\t\t\tlet __ea = {name}::encode(__a);\n"));
343 out.push_str(&format!("\t\t\t\tlet __eb = {name}::encode(__b);\n"));
344 out.push_str("\t\t\t\tlet __actual = __ea.as_slice().cmp(__eb.as_slice());\n");
345 out.push_str(
346 "\t\t\t\tassert_eq!(\n\t\t\t\t\t__expected,\n\t\t\t\t\t__expected_order(__b, \
347 __a).0.reverse(),\n\t\t\t\t\t\"the declared direction of column '{}' is not antisymmetric, so it \
348 cannot be a total order\",\n\t\t\t\t\t__column\n\t\t\t\t);\n",
349 );
350 out.push_str(&format!(
351 "\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 \
352 order disagrees with the declared direction of column '{{}}'\\n a = {{:?}}\\n b = {{:?}}\\n a \
353 bytes = {{:?}}\\n b bytes = {{:?}}\",\n\t\t\t\t\t__column,\n\t\t\t\t\t__a,\n\t\t\t\t\t\
354 __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"
355 ));
356 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 == \
357 __b,\n\t\t\t\t\t\"{name}: two distinct keys share an encoding, or one key encodes two ways\\n a \
358 = {{:?}}\\n b = {{:?}}\\n a bytes = {{:?}}\\n b bytes = \
359 {{:?}}\",\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\
360 __eb.as_slice()\n\t\t\t\t);\n"));
361 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\
362 __actual,\n\t\t\t\t\t\"{name}: fields() order disagrees with encoded byte order\\n a = \
363 {{:?}}\\n b = {{:?}}\\n a bytes = {{:?}}\\n b bytes = \
364 {{:?}}\",\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\
365 __eb.as_slice()\n\t\t\t\t);\n"));
366 out.push_str("\t\t\t}\n\t\t}\n\t}\n");
367 out
368}