1use std::fmt;
8
9use crate::ErrorCode;
10
11pub const MAX_PUBLIC_DIAGNOSTIC_FACTS: usize = 80;
13
14macro_rules! define_fact_tag_registry {
15 ($($name:ident = $raw:literal;)+) => {
16 #[derive(Clone, Copy, Eq, Hash, PartialEq)]
18 pub enum DiagnosticFactTag {
19 $(
20 #[doc = concat!("Public fact tag ", stringify!($raw), ".")]
21 $name,
22 )+
23 }
24
25 impl DiagnosticFactTag {
26 #[must_use]
28 pub const fn raw(self) -> u8 {
29 match self {
30 $(Self::$name => $raw,)+
31 }
32 }
33
34 #[must_use]
36 pub const fn known(raw: u8) -> Option<Self> {
37 match raw {
38 $($raw => Some(Self::$name),)+
39 _ => None,
40 }
41 }
42 }
43
44 #[cfg(test)]
45 const ORDERED_FACT_TAGS: &[DiagnosticFactTag] = &[
46 $(DiagnosticFactTag::$name,)+
47 ];
48 };
49}
50
51define_fact_tag_registry! {
54 AcceptedSchemaFingerprintMethod = 1;
55 AcceptedSchemaFingerprintHigh = 2;
56 AcceptedSchemaFingerprintLow = 3;
57 ExpectedFingerprintPrefix = 4;
58 ActualFingerprintPrefix = 5;
59 EntityTag = 6;
60 ExpectedEntityTag = 7;
61 ActualEntityTag = 8;
62 ConstraintId = 9;
63 FieldId = 10;
64 IndexId = 11;
65 RelationId = 12;
66 MutationOperation = 13;
67 RowOperation = 14;
68 BatchPosition = 15;
69 FirstBatchPosition = 16;
70 DuplicateBatchPosition = 17;
71 ClauseIndex = 18;
72 TermIndex = 19;
73 FirstTermIndex = 20;
74 DuplicateTermIndex = 21;
75 ProjectionIndex = 22;
76 GroupIndex = 23;
77 AggregateIndex = 24;
78 ArgumentIndex = 25;
79 BranchIndex = 26;
80 ComponentIndex = 27;
81 ParameterIndex = 28;
82 SourceSpanStart = 29;
83 SourceSpanEnd = 30;
84 Expected = 31;
85 Actual = 32;
86 Minimum = 33;
87 Maximum = 34;
88 Limit = 35;
89 ExpectedCount = 36;
90 ActualCount = 37;
91 ExpectedRevision = 38;
92 ActualRevision = 39;
93 CurrentRevision = 40;
94 RequestedRevision = 41;
95 ExpectedVersion = 42;
96 ActualVersion = 43;
97 CurrentVersion = 44;
98 RequestedVersion = 45;
99 ExpectedOffset = 46;
100 ActualOffset = 47;
101 ExpectedArity = 48;
102 ActualArity = 49;
103 ExpectedLength = 50;
104 ActualLength = 51;
105 ExpectedSlotCount = 52;
106 ActualSlotCount = 53;
107 RowLayout = 54;
108 HistoryFloor = 55;
109 CurrentLayout = 56;
110 PhysicalSlot = 57;
111 PhysicalGeneration = 58;
112 ExpectedMemoryId = 59;
113 ActualMemoryId = 60;
114 ConstraintKind = 61;
115 ConstraintContext = 62;
116 FieldKind = 63;
117 ValueKind = 64;
118 TypeFamily = 65;
119 FunctionKind = 66;
120 OperatorKind = 67;
121 AggregateKind = 68;
122 KeyNamespaceKind = 69;
123 ComponentKind = 70;
124 MismatchKind = 71;
125 DecodeReason = 72;
126 BudgetResource = 73;
127 MigrationPhase = 74;
128 DatabaseControlRecordKind = 75;
129 StateKind = 76;
130 PayloadComponent = 77;
131 ExpectedSignaturePrefix = 78;
132 ActualSignaturePrefix = 79;
133 FindingPosition = 80;
134 RootField = 81;
135 RecordMember = 82;
136 TupleElement = 83;
137 Newtype = 84;
138 EnumVariant = 85;
139 ListElement = 86;
140 SetElement = 87;
141 MapEntryKey = 88;
142 MapEntryValue = 89;
143}
144
145#[derive(Clone, Copy, Eq, Hash, PartialEq)]
150pub enum DiagnosticDecodeReason {
151 CursorEmpty,
152 CursorTooLong,
153 CursorOddLength,
154 CursorInvalidHex,
155 CursorGroupedDirectionMismatch,
156 CursorTokenEncode,
157 CursorTokenDecode,
158 RecoveryMarkerMagic,
159 RecoveryMarkerChecksum,
160 RecoveryMarkerState,
161}
162
163#[derive(Clone, Copy, Eq, Hash, PartialEq)]
165pub enum DiagnosticMutationOperation {
166 Insert,
167 Replace,
168 Update,
169 Delete,
170}
171
172macro_rules! define_numeric_fact_value_registry {
173 (
174 $(#[$enum_meta:meta])*
175 pub enum $name:ident {
176 $($variant:ident = $raw:literal;)+
177 }
178 ) => {
179 $(#[$enum_meta])*
180 #[derive(Clone, Copy, Eq, Hash, PartialEq)]
181 pub enum $name {
182 $(
183 #[doc = concat!("Compact diagnostic value ", stringify!($raw), ".")]
184 $variant,
185 )+
186 }
187
188 impl $name {
189 #[must_use]
191 pub const fn raw(self) -> u64 {
192 match self {
193 $(Self::$variant => $raw,)+
194 }
195 }
196
197 #[must_use]
199 pub const fn known(raw: u64) -> Option<Self> {
200 match raw {
201 $($raw => Some(Self::$variant),)+
202 _ => None,
203 }
204 }
205 }
206
207 impl fmt::Debug for $name {
208 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
209 write!(f, "{}", self.raw())
210 }
211 }
212 };
213}
214
215define_numeric_fact_value_registry! {
216 pub enum DiagnosticConstraintKind {
218 Check = 1;
219 NotNull = 2;
220 Relation = 3;
221 TargetedRule = 4;
222 Unique = 5;
223 }
224}
225
226define_numeric_fact_value_registry! {
227 pub enum DiagnosticConstraintContext {
229 Integrity = 1;
230 MigrationValidation = 2;
231 WriteAdmission = 3;
232 }
233}
234
235define_numeric_fact_value_registry! {
236 pub enum DiagnosticComponentKind {
238 CommitDataKey = 1;
239 IndexKey = 2;
240 IndexKeyComponent = 3;
241 RelationTargetPrimaryKey = 4;
242 }
243}
244
245define_numeric_fact_value_registry! {
246 pub enum DiagnosticTypeFamily {
248 Blob = 1;
249 Bool = 2;
250 Collection = 3;
251 Null = 4;
252 Numeric = 5;
253 Opaque = 6;
254 Structured = 7;
255 Text = 8;
256 Unknown = 9;
257 }
258}
259
260define_numeric_fact_value_registry! {
261 pub enum DiagnosticFunctionKind {
263 Abs = 1;
264 Cbrt = 2;
265 Ceiling = 3;
266 Coalesce = 4;
267 CollectionContains = 5;
268 Contains = 6;
269 EndsWith = 7;
270 Exp = 8;
271 Floor = 9;
272 IsEmpty = 10;
273 IsMissing = 11;
274 IsNotEmpty = 12;
275 IsNotNull = 13;
276 IsNull = 14;
277 Left = 15;
278 Length = 16;
279 Ln = 17;
280 Log = 18;
281 Log2 = 19;
282 Log10 = 20;
283 Lower = 21;
284 Ltrim = 22;
285 Mod = 23;
286 NullIf = 24;
287 OctetLength = 25;
288 Position = 26;
289 Power = 27;
290 Replace = 28;
291 Right = 29;
292 Round = 30;
293 Rtrim = 31;
294 Sign = 32;
295 Sqrt = 33;
296 StartsWith = 34;
297 Substring = 35;
298 Trim = 36;
299 Trunc = 37;
300 Upper = 38;
301 InList = 39;
302 }
303}
304
305define_numeric_fact_value_registry! {
306 pub enum DiagnosticOperatorKind {
308 Not = 1;
309 Add = 2;
310 And = 3;
311 Div = 4;
312 Eq = 5;
313 Gt = 6;
314 Gte = 7;
315 Lt = 8;
316 Lte = 9;
317 Mul = 10;
318 Ne = 11;
319 Or = 12;
320 Sub = 13;
321 In = 14;
322 NotIn = 15;
323 Contains = 16;
324 StartsWith = 17;
325 EndsWith = 18;
326 }
327}
328
329define_numeric_fact_value_registry! {
330 pub enum DiagnosticAggregateKind {
332 Count = 1;
333 Sum = 2;
334 Avg = 3;
335 Exists = 4;
336 Min = 5;
337 Max = 6;
338 First = 7;
339 Last = 8;
340 }
341}
342
343impl DiagnosticDecodeReason {
344 #[must_use]
346 pub const fn raw(self) -> u64 {
347 match self {
348 Self::CursorEmpty => 1,
349 Self::CursorTooLong => 2,
350 Self::CursorOddLength => 3,
351 Self::CursorInvalidHex => 4,
352 Self::CursorGroupedDirectionMismatch => 5,
353 Self::CursorTokenEncode => 6,
354 Self::CursorTokenDecode => 7,
355 Self::RecoveryMarkerMagic => 8,
356 Self::RecoveryMarkerChecksum => 9,
357 Self::RecoveryMarkerState => 10,
358 }
359 }
360
361 #[must_use]
363 pub const fn known(raw: u64) -> Option<Self> {
364 match raw {
365 1 => Some(Self::CursorEmpty),
366 2 => Some(Self::CursorTooLong),
367 3 => Some(Self::CursorOddLength),
368 4 => Some(Self::CursorInvalidHex),
369 5 => Some(Self::CursorGroupedDirectionMismatch),
370 6 => Some(Self::CursorTokenEncode),
371 7 => Some(Self::CursorTokenDecode),
372 8 => Some(Self::RecoveryMarkerMagic),
373 9 => Some(Self::RecoveryMarkerChecksum),
374 10 => Some(Self::RecoveryMarkerState),
375 _ => None,
376 }
377 }
378}
379
380impl DiagnosticMutationOperation {
381 #[must_use]
383 pub const fn raw(self) -> u64 {
384 match self {
385 Self::Insert => 1,
386 Self::Replace => 2,
387 Self::Update => 3,
388 Self::Delete => 4,
389 }
390 }
391
392 #[must_use]
394 pub const fn known(raw: u64) -> Option<Self> {
395 match raw {
396 1 => Some(Self::Insert),
397 2 => Some(Self::Replace),
398 3 => Some(Self::Update),
399 4 => Some(Self::Delete),
400 _ => None,
401 }
402 }
403}
404
405impl fmt::Debug for DiagnosticFactTag {
406 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
407 write!(f, "{}", self.raw())
408 }
409}
410
411impl fmt::Debug for DiagnosticDecodeReason {
412 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
413 write!(f, "{}", self.raw())
414 }
415}
416
417impl fmt::Debug for DiagnosticMutationOperation {
418 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
419 write!(f, "{}", self.raw())
420 }
421}
422
423#[must_use]
425pub const fn pack_u32_pair(high: u32, low: u32) -> u64 {
426 (high as u64) << 32 | low as u64
427}
428
429#[must_use]
431pub const fn unpack_u32_pair(value: u64) -> (u32, u32) {
432 let bytes = value.to_be_bytes();
433 (
434 u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]),
435 u32::from_be_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]),
436 )
437}
438
439#[derive(Clone, Copy, Debug, Eq, PartialEq)]
443pub enum DiagnosticFactSchemaMismatch {
444 GlobalMaximumExceeded,
446 CodeMaximumExceeded,
448 InvalidSequence,
450 InvalidValue,
452}
453
454pub fn validate_known_diagnostic_fact_schema(
459 code: ErrorCode,
460 facts: &[(DiagnosticFactTag, u64)],
461) -> Result<(), DiagnosticFactSchemaMismatch> {
462 validate_diagnostic_fact_schema(code, facts.len(), |index| {
463 let (tag, value) = facts[index];
464 (tag.raw(), value)
465 })
466}
467
468pub fn validate_raw_diagnostic_fact_schema(
473 code: ErrorCode,
474 facts: &[(u8, u64)],
475) -> Result<(), DiagnosticFactSchemaMismatch> {
476 validate_diagnostic_fact_schema(code, facts.len(), |index| facts[index])
477}
478
479#[expect(
480 clippy::too_many_lines,
481 reason = "the frozen E-code schema registry keeps every numeric owner visible in one exhaustive dispatch"
482)]
483fn validate_diagnostic_fact_schema(
484 code: ErrorCode,
485 fact_count: usize,
486 fact_at: impl Fn(usize) -> (u8, u64),
487) -> Result<(), DiagnosticFactSchemaMismatch> {
488 if fact_count > MAX_PUBLIC_DIAGNOSTIC_FACTS {
489 return Err(DiagnosticFactSchemaMismatch::GlobalMaximumExceeded);
490 }
491
492 let maximum = diagnostic_fact_maximum(code);
493 if fact_count > maximum {
494 return Err(DiagnosticFactSchemaMismatch::CodeMaximumExceeded);
495 }
496
497 let valid_sequence = match code.raw() {
498 3 => query_plan_schema(fact_count, &fact_at),
499 6 => cursor_schema(fact_count, &fact_at),
500 16 => store_corruption_schema(fact_count, &fact_at),
501 18 => runtime_corruption_schema(fact_count, &fact_at),
502 19 => incompatible_format_schema(fact_count, &fact_at),
503 20 => runtime_invariant_schema(fact_count, &fact_at),
504 21 => runtime_conflict_schema(fact_count, &fact_at),
505 23 => runtime_unsupported_schema(fact_count, &fact_at),
506 24 => runtime_internal_schema(fact_count, &fact_at),
507 138 => tags_match(
508 fact_count,
509 &fact_at,
510 &[
511 DiagnosticFactTag::ExpectedArity,
512 DiagnosticFactTag::ActualArity,
513 ],
514 ),
515 141 | 142 | 169 | 170 => {
516 tags_match(fact_count, &fact_at, &[DiagnosticFactTag::ProjectionIndex])
517 }
518 175 => tags_match(fact_count, &fact_at, &[DiagnosticFactTag::ParameterIndex]),
519 177 | 237 => {
520 tags_match(fact_count, &fact_at, &[DiagnosticFactTag::Limit])
521 || tags_match(
522 fact_count,
523 &fact_at,
524 &[DiagnosticFactTag::ActualLength, DiagnosticFactTag::Limit],
525 )
526 }
527 178 | 180 | 202 | 203 | 205 | 236 => tags_match(
528 fact_count,
529 &fact_at,
530 &[DiagnosticFactTag::ActualCount, DiagnosticFactTag::Limit],
531 ),
532 196 | 234 => tags_match(
533 fact_count,
534 &fact_at,
535 &[
536 DiagnosticFactTag::EntityTag,
537 DiagnosticFactTag::FieldId,
538 DiagnosticFactTag::MutationOperation,
539 DiagnosticFactTag::BatchPosition,
540 ],
541 ),
542 197 => tags_match(
543 fact_count,
544 &fact_at,
545 &[
546 DiagnosticFactTag::RowLayout,
547 DiagnosticFactTag::HistoryFloor,
548 DiagnosticFactTag::CurrentLayout,
549 ],
550 ),
551 198 => tags_match(
552 fact_count,
553 &fact_at,
554 &[
555 DiagnosticFactTag::RowLayout,
556 DiagnosticFactTag::ExpectedSlotCount,
557 DiagnosticFactTag::ActualSlotCount,
558 ],
559 ),
560 201 => tags_match(
561 fact_count,
562 &fact_at,
563 &[DiagnosticFactTag::ActualCount, DiagnosticFactTag::Minimum],
564 ),
565 223 => constraint_schema(fact_count, &fact_at, true),
566 225 => constraint_schema(fact_count, &fact_at, false),
567 233 => tags_match(
568 fact_count,
569 &fact_at,
570 &[
571 DiagnosticFactTag::EntityTag,
572 DiagnosticFactTag::MutationOperation,
573 DiagnosticFactTag::BatchPosition,
574 ],
575 ),
576 235 => {
577 tags_match(fact_count, &fact_at, &[DiagnosticFactTag::ActualCount]) && fact_at(0).1 == 0
578 }
579 238 => tags_match(
580 fact_count,
581 &fact_at,
582 &[DiagnosticFactTag::ActualLength, DiagnosticFactTag::Limit],
583 ),
584 239 => tags_match(
585 fact_count,
586 &fact_at,
587 &[
588 DiagnosticFactTag::BatchPosition,
589 DiagnosticFactTag::ExpectedEntityTag,
590 DiagnosticFactTag::ActualEntityTag,
591 ],
592 ),
593 240 => tags_match(
594 fact_count,
595 &fact_at,
596 &[
597 DiagnosticFactTag::EntityTag,
598 DiagnosticFactTag::FirstBatchPosition,
599 DiagnosticFactTag::DuplicateBatchPosition,
600 ],
601 ),
602 _ => fact_count == 0,
603 };
604 if !valid_sequence {
605 return Err(DiagnosticFactSchemaMismatch::InvalidSequence);
606 }
607
608 for index in 0..fact_count {
609 let (raw_tag, value) = fact_at(index);
610 let Some(tag) = DiagnosticFactTag::known(raw_tag) else {
611 return Err(DiagnosticFactSchemaMismatch::InvalidSequence);
612 };
613 if !diagnostic_fact_value_is_valid(tag, value) {
614 return Err(DiagnosticFactSchemaMismatch::InvalidValue);
615 }
616 }
617 Ok(())
618}
619
620const fn diagnostic_fact_maximum(code: ErrorCode) -> usize {
621 match code.raw() {
622 6 | 16 | 18 | 24 | 197 | 198 | 233 | 239 | 240 => 3,
623 141 | 142 | 169 | 170 | 175 | 235 => 1,
624 19 | 21 | 138 | 177 | 178 | 180 | 201 | 202 | 203 | 205 | 236 | 237 | 238 => 2,
625 3 | 20 => 5,
626 23 => 6,
627 196 | 234 => 4,
628 223 => 73,
629 225 => 9,
630 _ => 0,
631 }
632}
633
634#[expect(
635 clippy::too_many_lines,
636 reason = "the query-plan E-code deliberately owns several exact finite fact sequences"
637)]
638fn query_plan_schema(fact_count: usize, fact_at: &impl Fn(usize) -> (u8, u64)) -> bool {
639 fact_count == 0
640 || tags_match(fact_count, fact_at, &[DiagnosticFactTag::TermIndex])
641 || tags_match(fact_count, fact_at, &[DiagnosticFactTag::ComponentIndex])
642 || tags_match(fact_count, fact_at, &[DiagnosticFactTag::GroupIndex])
643 || tags_match(fact_count, fact_at, &[DiagnosticFactTag::ClauseIndex])
644 || tags_match(fact_count, fact_at, &[DiagnosticFactTag::AggregateIndex])
645 || tags_match(fact_count, fact_at, &[DiagnosticFactTag::AggregateKind])
646 || tags_match(fact_count, fact_at, &[DiagnosticFactTag::ProjectionIndex])
647 || tags_match(
648 fact_count,
649 fact_at,
650 &[
651 DiagnosticFactTag::FirstTermIndex,
652 DiagnosticFactTag::DuplicateTermIndex,
653 ],
654 )
655 || tags_match(
656 fact_count,
657 fact_at,
658 &[
659 DiagnosticFactTag::ClauseIndex,
660 DiagnosticFactTag::OperatorKind,
661 ],
662 )
663 || tags_match(
664 fact_count,
665 fact_at,
666 &[
667 DiagnosticFactTag::AggregateIndex,
668 DiagnosticFactTag::AggregateKind,
669 ],
670 )
671 || tags_match(
672 fact_count,
673 fact_at,
674 &[
675 DiagnosticFactTag::AggregateKind,
676 DiagnosticFactTag::TypeFamily,
677 ],
678 )
679 || tags_match(
680 fact_count,
681 fact_at,
682 &[
683 DiagnosticFactTag::OperatorKind,
684 DiagnosticFactTag::TypeFamily,
685 ],
686 )
687 || tags_match(
688 fact_count,
689 fact_at,
690 &[
691 DiagnosticFactTag::BranchIndex,
692 DiagnosticFactTag::TypeFamily,
693 ],
694 )
695 || tags_match(
696 fact_count,
697 fact_at,
698 &[DiagnosticFactTag::TypeFamily, DiagnosticFactTag::TypeFamily],
699 )
700 || tags_match(
701 fact_count,
702 fact_at,
703 &[
704 DiagnosticFactTag::ClauseIndex,
705 DiagnosticFactTag::AggregateIndex,
706 DiagnosticFactTag::ActualCount,
707 ],
708 )
709 || tags_match(
710 fact_count,
711 fact_at,
712 &[
713 DiagnosticFactTag::FunctionKind,
714 DiagnosticFactTag::ExpectedArity,
715 DiagnosticFactTag::ActualArity,
716 ],
717 )
718 || tags_match(
719 fact_count,
720 fact_at,
721 &[
722 DiagnosticFactTag::FunctionKind,
723 DiagnosticFactTag::ArgumentIndex,
724 DiagnosticFactTag::TypeFamily,
725 ],
726 )
727 || tags_match(
728 fact_count,
729 fact_at,
730 &[
731 DiagnosticFactTag::OperatorKind,
732 DiagnosticFactTag::TypeFamily,
733 DiagnosticFactTag::TypeFamily,
734 ],
735 )
736 || tags_match(
737 fact_count,
738 fact_at,
739 &[
740 DiagnosticFactTag::BranchIndex,
741 DiagnosticFactTag::TypeFamily,
742 DiagnosticFactTag::TypeFamily,
743 ],
744 )
745 || tags_match(
746 fact_count,
747 fact_at,
748 &[
749 DiagnosticFactTag::TypeFamily,
750 DiagnosticFactTag::BranchIndex,
751 DiagnosticFactTag::TypeFamily,
752 ],
753 )
754 || tags_match(
755 fact_count,
756 fact_at,
757 &[
758 DiagnosticFactTag::BranchIndex,
759 DiagnosticFactTag::TypeFamily,
760 DiagnosticFactTag::BranchIndex,
761 DiagnosticFactTag::TypeFamily,
762 ],
763 )
764 || tags_match(
765 fact_count,
766 fact_at,
767 &[
768 DiagnosticFactTag::FunctionKind,
769 DiagnosticFactTag::ArgumentIndex,
770 DiagnosticFactTag::TypeFamily,
771 DiagnosticFactTag::ArgumentIndex,
772 DiagnosticFactTag::TypeFamily,
773 ],
774 )
775}
776
777fn cursor_schema(fact_count: usize, fact_at: &impl Fn(usize) -> (u8, u64)) -> bool {
778 if fact_count == 0 {
779 return true;
780 }
781 if tags_match(fact_count, fact_at, &[DiagnosticFactTag::DecodeReason]) {
782 return matches!(fact_at(0).1, 1 | 3 | 5 | 6 | 7);
783 }
784 if tags_match(
785 fact_count,
786 fact_at,
787 &[
788 DiagnosticFactTag::ActualLength,
789 DiagnosticFactTag::Maximum,
790 DiagnosticFactTag::DecodeReason,
791 ],
792 ) {
793 return fact_at(2).1 == DiagnosticDecodeReason::CursorTooLong.raw();
794 }
795 if tags_match(
796 fact_count,
797 fact_at,
798 &[
799 DiagnosticFactTag::ComponentIndex,
800 DiagnosticFactTag::DecodeReason,
801 ],
802 ) {
803 return matches!(fact_at(1).1, 4 | 6 | 7);
804 }
805 tags_match(
806 fact_count,
807 fact_at,
808 &[
809 DiagnosticFactTag::ExpectedSignaturePrefix,
810 DiagnosticFactTag::ActualSignaturePrefix,
811 ],
812 ) || tags_match(
813 fact_count,
814 fact_at,
815 &[
816 DiagnosticFactTag::ExpectedOffset,
817 DiagnosticFactTag::ActualOffset,
818 ],
819 )
820}
821
822fn store_corruption_schema(fact_count: usize, fact_at: &impl Fn(usize) -> (u8, u64)) -> bool {
823 fact_count == 0
824 || (tags_match(
825 fact_count,
826 fact_at,
827 &[
828 DiagnosticFactTag::ComponentKind,
829 DiagnosticFactTag::ActualLength,
830 DiagnosticFactTag::Limit,
831 ],
832 ) && fact_at(0).1 == DiagnosticComponentKind::CommitDataKey.raw())
833 || tags_match(
834 fact_count,
835 fact_at,
836 &[
837 DiagnosticFactTag::ExpectedEntityTag,
838 DiagnosticFactTag::ActualEntityTag,
839 ],
840 )
841}
842
843fn runtime_corruption_schema(fact_count: usize, fact_at: &impl Fn(usize) -> (u8, u64)) -> bool {
844 store_corruption_schema(fact_count, fact_at)
845 || (tags_match(fact_count, fact_at, &[DiagnosticFactTag::DecodeReason])
846 && matches!(fact_at(0).1, 8..=10))
847}
848
849fn incompatible_format_schema(fact_count: usize, fact_at: &impl Fn(usize) -> (u8, u64)) -> bool {
850 fact_count == 0
851 || tags_match(fact_count, fact_at, &[DiagnosticFactTag::ExpectedVersion])
852 || tags_match(
853 fact_count,
854 fact_at,
855 &[
856 DiagnosticFactTag::ExpectedVersion,
857 DiagnosticFactTag::ActualVersion,
858 ],
859 )
860}
861
862fn runtime_invariant_schema(fact_count: usize, fact_at: &impl Fn(usize) -> (u8, u64)) -> bool {
863 fact_count == 0
864 || (tags_match(
865 fact_count,
866 fact_at,
867 &[
868 DiagnosticFactTag::EntityTag,
869 DiagnosticFactTag::PhysicalGeneration,
870 DiagnosticFactTag::ComponentKind,
871 DiagnosticFactTag::ActualArity,
872 DiagnosticFactTag::Maximum,
873 ],
874 ) && fact_at(2).1 == DiagnosticComponentKind::IndexKey.raw())
875}
876
877fn runtime_conflict_schema(fact_count: usize, fact_at: &impl Fn(usize) -> (u8, u64)) -> bool {
878 fact_count == 0
879 || tags_match(fact_count, fact_at, &[DiagnosticFactTag::ExpectedRevision])
880 || tags_match(
881 fact_count,
882 fact_at,
883 &[
884 DiagnosticFactTag::ExpectedRevision,
885 DiagnosticFactTag::CurrentRevision,
886 ],
887 )
888}
889
890fn runtime_unsupported_schema(fact_count: usize, fact_at: &impl Fn(usize) -> (u8, u64)) -> bool {
891 fact_count == 0
892 || (tags_match(
893 fact_count,
894 fact_at,
895 &[
896 DiagnosticFactTag::EntityTag,
897 DiagnosticFactTag::PhysicalGeneration,
898 DiagnosticFactTag::ComponentIndex,
899 DiagnosticFactTag::ComponentKind,
900 DiagnosticFactTag::ActualLength,
901 DiagnosticFactTag::Limit,
902 ],
903 ) && fact_at(3).1 == DiagnosticComponentKind::IndexKeyComponent.raw())
904}
905
906fn runtime_internal_schema(fact_count: usize, fact_at: &impl Fn(usize) -> (u8, u64)) -> bool {
907 fact_count == 0
908 || tags_match(
909 fact_count,
910 fact_at,
911 &[
912 DiagnosticFactTag::ExpectedMemoryId,
913 DiagnosticFactTag::ActualMemoryId,
914 ],
915 )
916 || (tags_match(
917 fact_count,
918 fact_at,
919 &[
920 DiagnosticFactTag::ComponentKind,
921 DiagnosticFactTag::ExpectedArity,
922 DiagnosticFactTag::ActualArity,
923 ],
924 ) && fact_at(0).1 == DiagnosticComponentKind::RelationTargetPrimaryKey.raw())
925}
926
927fn constraint_schema(
928 fact_count: usize,
929 fact_at: &impl Fn(usize) -> (u8, u64),
930 allow_targeted_path: bool,
931) -> bool {
932 const COMMON: &[DiagnosticFactTag] = &[
933 DiagnosticFactTag::AcceptedSchemaFingerprintMethod,
934 DiagnosticFactTag::AcceptedSchemaFingerprintHigh,
935 DiagnosticFactTag::AcceptedSchemaFingerprintLow,
936 DiagnosticFactTag::EntityTag,
937 DiagnosticFactTag::ConstraintId,
938 DiagnosticFactTag::ConstraintKind,
939 DiagnosticFactTag::ConstraintContext,
940 ];
941 if fact_count < COMMON.len()
942 || !tags_prefix_matches(fact_count, fact_at, COMMON)
943 || fact_at(6).1 != DiagnosticConstraintContext::WriteAdmission.raw()
944 {
945 return false;
946 }
947
948 let constraint_kind = fact_at(5).1;
949 if DiagnosticConstraintKind::known(constraint_kind).is_none() {
950 return false;
951 }
952 let mut index = COMMON.len();
953 if index < fact_count && fact_at(index).0 == DiagnosticFactTag::MutationOperation.raw() {
954 index += 1;
955 if index < fact_count && fact_at(index).0 == DiagnosticFactTag::BatchPosition.raw() {
956 index += 1;
957 }
958 }
959
960 let path_len = fact_count - index;
961 if path_len == 0 {
962 return !allow_targeted_path
963 || constraint_kind != DiagnosticConstraintKind::TargetedRule.raw();
964 }
965 allow_targeted_path
966 && constraint_kind == DiagnosticConstraintKind::TargetedRule.raw()
967 && path_len <= 64
968 && (index..fact_count).all(|position| {
969 DiagnosticFactTag::known(fact_at(position).0).is_some_and(is_value_path_tag)
970 })
971}
972
973fn tags_match(
974 fact_count: usize,
975 fact_at: &impl Fn(usize) -> (u8, u64),
976 expected: &[DiagnosticFactTag],
977) -> bool {
978 fact_count == expected.len() && tags_prefix_matches(fact_count, fact_at, expected)
979}
980
981fn tags_prefix_matches(
982 fact_count: usize,
983 fact_at: &impl Fn(usize) -> (u8, u64),
984 expected: &[DiagnosticFactTag],
985) -> bool {
986 fact_count >= expected.len()
987 && expected
988 .iter()
989 .enumerate()
990 .all(|(index, tag)| fact_at(index).0 == tag.raw())
991}
992
993const fn is_value_path_tag(tag: DiagnosticFactTag) -> bool {
994 matches!(
995 tag,
996 DiagnosticFactTag::RootField
997 | DiagnosticFactTag::RecordMember
998 | DiagnosticFactTag::TupleElement
999 | DiagnosticFactTag::Newtype
1000 | DiagnosticFactTag::EnumVariant
1001 | DiagnosticFactTag::ListElement
1002 | DiagnosticFactTag::SetElement
1003 | DiagnosticFactTag::MapEntryKey
1004 | DiagnosticFactTag::MapEntryValue
1005 )
1006}
1007
1008const fn diagnostic_fact_value_is_valid(tag: DiagnosticFactTag, value: u64) -> bool {
1009 match tag {
1010 DiagnosticFactTag::AcceptedSchemaFingerprintMethod
1011 | DiagnosticFactTag::ExpectedMemoryId
1012 | DiagnosticFactTag::ActualMemoryId => value <= u8::MAX as u64,
1013 DiagnosticFactTag::ConstraintId
1014 | DiagnosticFactTag::FieldId
1015 | DiagnosticFactTag::IndexId
1016 | DiagnosticFactTag::RelationId
1017 | DiagnosticFactTag::BatchPosition
1018 | DiagnosticFactTag::FirstBatchPosition
1019 | DiagnosticFactTag::DuplicateBatchPosition
1020 | DiagnosticFactTag::RowLayout
1021 | DiagnosticFactTag::HistoryFloor
1022 | DiagnosticFactTag::CurrentLayout
1023 | DiagnosticFactTag::RootField
1024 | DiagnosticFactTag::Newtype
1025 | DiagnosticFactTag::ListElement
1026 | DiagnosticFactTag::SetElement
1027 | DiagnosticFactTag::MapEntryKey
1028 | DiagnosticFactTag::MapEntryValue => value <= u32::MAX as u64,
1029 DiagnosticFactTag::ConstraintKind => DiagnosticConstraintKind::known(value).is_some(),
1030 DiagnosticFactTag::ConstraintContext => DiagnosticConstraintContext::known(value).is_some(),
1031 DiagnosticFactTag::TypeFamily => DiagnosticTypeFamily::known(value).is_some(),
1032 DiagnosticFactTag::FunctionKind => DiagnosticFunctionKind::known(value).is_some(),
1033 DiagnosticFactTag::OperatorKind => DiagnosticOperatorKind::known(value).is_some(),
1034 DiagnosticFactTag::AggregateKind => DiagnosticAggregateKind::known(value).is_some(),
1035 DiagnosticFactTag::ComponentKind => DiagnosticComponentKind::known(value).is_some(),
1036 DiagnosticFactTag::DecodeReason => DiagnosticDecodeReason::known(value).is_some(),
1037 DiagnosticFactTag::MutationOperation => DiagnosticMutationOperation::known(value).is_some(),
1038 _ => true,
1039 }
1040}
1041
1042#[cfg(test)]
1043mod tests {
1044 use super::{
1045 DiagnosticAggregateKind, DiagnosticComponentKind, DiagnosticConstraintContext,
1046 DiagnosticConstraintKind, DiagnosticDecodeReason, DiagnosticFactSchemaMismatch,
1047 DiagnosticFactTag, DiagnosticFunctionKind, DiagnosticMutationOperation,
1048 DiagnosticOperatorKind, DiagnosticTypeFamily, ORDERED_FACT_TAGS, pack_u32_pair,
1049 unpack_u32_pair, validate_known_diagnostic_fact_schema,
1050 validate_raw_diagnostic_fact_schema,
1051 };
1052 use crate::ErrorCode;
1053
1054 #[test]
1055 fn fact_tag_registry_is_fixed_unique_and_contiguous() {
1056 for (index, tag) in ORDERED_FACT_TAGS.iter().copied().enumerate() {
1057 let expected = u8::try_from(index + 1).expect("fact-tag index fits u8");
1058 assert_eq!(tag.raw(), expected);
1059 assert_eq!(DiagnosticFactTag::known(expected), Some(tag));
1060 }
1061
1062 assert_eq!(DiagnosticFactTag::known(0), None);
1063 assert_eq!(DiagnosticFactTag::known(90), None);
1064 assert_eq!(DiagnosticFactTag::known(u8::MAX), None);
1065 }
1066
1067 #[test]
1068 fn accepted_identity_pair_packing_is_exact() {
1069 for pair in [
1070 (0, 0),
1071 (1, 2),
1072 (u32::MAX, 0),
1073 (0, u32::MAX),
1074 (u32::MAX, u32::MAX),
1075 ] {
1076 assert_eq!(unpack_u32_pair(pack_u32_pair(pair.0, pair.1)), pair);
1077 }
1078 }
1079
1080 #[test]
1081 fn constraint_fact_value_registries_are_fixed() {
1082 assert_eq!(DiagnosticConstraintKind::Check.raw(), 1);
1083 assert_eq!(DiagnosticConstraintKind::NotNull.raw(), 2);
1084 assert_eq!(DiagnosticConstraintKind::Relation.raw(), 3);
1085 assert_eq!(DiagnosticConstraintKind::TargetedRule.raw(), 4);
1086 assert_eq!(DiagnosticConstraintKind::Unique.raw(), 5);
1087 assert_eq!(DiagnosticConstraintKind::known(0), None);
1088 assert_eq!(DiagnosticConstraintKind::known(6), None);
1089
1090 assert_eq!(DiagnosticConstraintContext::Integrity.raw(), 1);
1091 assert_eq!(DiagnosticConstraintContext::MigrationValidation.raw(), 2);
1092 assert_eq!(DiagnosticConstraintContext::WriteAdmission.raw(), 3);
1093 assert_eq!(DiagnosticConstraintContext::known(0), None);
1094 assert_eq!(DiagnosticConstraintContext::known(4), None);
1095 }
1096
1097 #[test]
1098 fn component_kind_registry_is_fixed_and_numeric() {
1099 let kinds = [
1100 DiagnosticComponentKind::CommitDataKey,
1101 DiagnosticComponentKind::IndexKey,
1102 DiagnosticComponentKind::IndexKeyComponent,
1103 DiagnosticComponentKind::RelationTargetPrimaryKey,
1104 ];
1105
1106 for (index, kind) in kinds.iter().copied().enumerate() {
1107 let expected = (index + 1) as u64;
1108 assert_eq!(kind.raw(), expected);
1109 assert_eq!(DiagnosticComponentKind::known(expected), Some(kind));
1110 assert_eq!(format!("{kind:?}"), expected.to_string());
1111 }
1112 assert_eq!(DiagnosticComponentKind::known(0), None);
1113 assert_eq!(DiagnosticComponentKind::known(5), None);
1114 }
1115
1116 #[test]
1117 fn decode_reason_registry_is_fixed_and_numeric() {
1118 let reasons = [
1119 DiagnosticDecodeReason::CursorEmpty,
1120 DiagnosticDecodeReason::CursorTooLong,
1121 DiagnosticDecodeReason::CursorOddLength,
1122 DiagnosticDecodeReason::CursorInvalidHex,
1123 DiagnosticDecodeReason::CursorGroupedDirectionMismatch,
1124 DiagnosticDecodeReason::CursorTokenEncode,
1125 DiagnosticDecodeReason::CursorTokenDecode,
1126 DiagnosticDecodeReason::RecoveryMarkerMagic,
1127 DiagnosticDecodeReason::RecoveryMarkerChecksum,
1128 DiagnosticDecodeReason::RecoveryMarkerState,
1129 ];
1130
1131 for (index, reason) in reasons.iter().copied().enumerate() {
1132 let expected = (index + 1) as u64;
1133 assert_eq!(reason.raw(), expected);
1134 assert_eq!(DiagnosticDecodeReason::known(expected), Some(reason));
1135 assert_eq!(format!("{reason:?}"), expected.to_string());
1136 }
1137
1138 assert_eq!(DiagnosticDecodeReason::known(0), None);
1139 assert_eq!(DiagnosticDecodeReason::known(11), None);
1140 }
1141
1142 #[test]
1143 fn mutation_operation_registry_is_fixed_and_numeric() {
1144 let operations = [
1145 DiagnosticMutationOperation::Insert,
1146 DiagnosticMutationOperation::Replace,
1147 DiagnosticMutationOperation::Update,
1148 DiagnosticMutationOperation::Delete,
1149 ];
1150
1151 for (index, operation) in operations.iter().copied().enumerate() {
1152 let expected = (index + 1) as u64;
1153 assert_eq!(operation.raw(), expected);
1154 assert_eq!(
1155 DiagnosticMutationOperation::known(expected),
1156 Some(operation)
1157 );
1158 assert_eq!(format!("{operation:?}"), expected.to_string());
1159 }
1160
1161 assert_eq!(DiagnosticMutationOperation::known(0), None);
1162 assert_eq!(DiagnosticMutationOperation::known(5), None);
1163 }
1164
1165 #[test]
1166 fn query_kind_registries_are_fixed_contiguous_and_numeric() {
1167 for raw in 1..=9 {
1168 let value = DiagnosticTypeFamily::known(raw).expect("type family should be known");
1169 assert_eq!(value.raw(), raw);
1170 assert_eq!(format!("{value:?}"), raw.to_string());
1171 }
1172 assert_eq!(DiagnosticTypeFamily::known(0), None);
1173 assert_eq!(DiagnosticTypeFamily::known(10), None);
1174
1175 for raw in 1..=39 {
1176 let value = DiagnosticFunctionKind::known(raw).expect("function kind should be known");
1177 assert_eq!(value.raw(), raw);
1178 assert_eq!(format!("{value:?}"), raw.to_string());
1179 }
1180 assert_eq!(DiagnosticFunctionKind::known(0), None);
1181 assert_eq!(DiagnosticFunctionKind::known(40), None);
1182
1183 for raw in 1..=18 {
1184 let value = DiagnosticOperatorKind::known(raw).expect("operator kind should be known");
1185 assert_eq!(value.raw(), raw);
1186 assert_eq!(format!("{value:?}"), raw.to_string());
1187 }
1188 assert_eq!(DiagnosticOperatorKind::known(0), None);
1189 assert_eq!(DiagnosticOperatorKind::known(19), None);
1190
1191 for raw in 1..=8 {
1192 let value =
1193 DiagnosticAggregateKind::known(raw).expect("aggregate kind should be known");
1194 assert_eq!(value.raw(), raw);
1195 assert_eq!(format!("{value:?}"), raw.to_string());
1196 }
1197 assert_eq!(DiagnosticAggregateKind::known(0), None);
1198 assert_eq!(DiagnosticAggregateKind::known(9), None);
1199 }
1200
1201 #[test]
1202 fn per_code_schema_rejects_missing_disallowed_and_noncanonical_tags() {
1203 let valid = [
1204 (DiagnosticFactTag::ActualCount, 5),
1205 (DiagnosticFactTag::Limit, 4),
1206 ];
1207 assert_eq!(
1208 validate_known_diagnostic_fact_schema(
1209 ErrorCode::RUNTIME_BOUNDARY_MUTATION_BATCH_TOO_MANY_ITEMS,
1210 &valid,
1211 ),
1212 Ok(())
1213 );
1214 assert_eq!(
1215 validate_known_diagnostic_fact_schema(
1216 ErrorCode::RUNTIME_BOUNDARY_MUTATION_BATCH_TOO_MANY_ITEMS,
1217 &valid[..1],
1218 ),
1219 Err(DiagnosticFactSchemaMismatch::InvalidSequence)
1220 );
1221 assert_eq!(
1222 validate_known_diagnostic_fact_schema(
1223 ErrorCode::RUNTIME_BOUNDARY_MUTATION_BATCH_TOO_MANY_ITEMS,
1224 &[valid[1], valid[0]],
1225 ),
1226 Err(DiagnosticFactSchemaMismatch::InvalidSequence)
1227 );
1228 assert_eq!(
1229 validate_known_diagnostic_fact_schema(ErrorCode::QUERY_VALIDATE, &valid),
1230 Err(DiagnosticFactSchemaMismatch::CodeMaximumExceeded)
1231 );
1232 }
1233
1234 #[test]
1235 fn raw_schema_keeps_unknown_context_numeric_but_marks_it_invalid() {
1236 assert_eq!(
1237 validate_raw_diagnostic_fact_schema(
1238 ErrorCode::QUERY_INVALID_CONTINUATION_CURSOR,
1239 &[(u8::MAX, 17)],
1240 ),
1241 Err(DiagnosticFactSchemaMismatch::InvalidSequence)
1242 );
1243 assert_eq!(
1244 validate_raw_diagnostic_fact_schema(
1245 ErrorCode::QUERY_INVALID_CONTINUATION_CURSOR,
1246 &[(DiagnosticFactTag::DecodeReason.raw(), u64::MAX)],
1247 ),
1248 Err(DiagnosticFactSchemaMismatch::InvalidSequence)
1249 );
1250 }
1251
1252 #[test]
1253 fn constraint_schema_enforces_authority_operation_and_bounded_path_suffix() {
1254 let mut targeted = vec![
1255 (DiagnosticFactTag::AcceptedSchemaFingerprintMethod, 1),
1256 (DiagnosticFactTag::AcceptedSchemaFingerprintHigh, 2),
1257 (DiagnosticFactTag::AcceptedSchemaFingerprintLow, 3),
1258 (DiagnosticFactTag::EntityTag, 17),
1259 (DiagnosticFactTag::ConstraintId, 4),
1260 (
1261 DiagnosticFactTag::ConstraintKind,
1262 DiagnosticConstraintKind::TargetedRule.raw(),
1263 ),
1264 (
1265 DiagnosticFactTag::ConstraintContext,
1266 DiagnosticConstraintContext::WriteAdmission.raw(),
1267 ),
1268 (
1269 DiagnosticFactTag::MutationOperation,
1270 DiagnosticMutationOperation::Insert.raw(),
1271 ),
1272 (DiagnosticFactTag::BatchPosition, 0),
1273 ];
1274 targeted.extend((0..64).map(|index| (DiagnosticFactTag::ListElement, index)));
1275 assert_eq!(targeted.len(), 73);
1276 assert_eq!(
1277 validate_known_diagnostic_fact_schema(
1278 ErrorCode::RUNTIME_BOUNDARY_CONSTRAINT_VIOLATION,
1279 targeted.as_slice(),
1280 ),
1281 Ok(())
1282 );
1283
1284 let mut overlong = targeted.clone();
1285 overlong.push((DiagnosticFactTag::ListElement, 64));
1286 assert_eq!(
1287 validate_known_diagnostic_fact_schema(
1288 ErrorCode::RUNTIME_BOUNDARY_CONSTRAINT_VIOLATION,
1289 overlong.as_slice(),
1290 ),
1291 Err(DiagnosticFactSchemaMismatch::CodeMaximumExceeded)
1292 );
1293
1294 let mut non_targeted_path = targeted;
1295 non_targeted_path[5].1 = DiagnosticConstraintKind::Unique.raw();
1296 assert_eq!(
1297 validate_known_diagnostic_fact_schema(
1298 ErrorCode::RUNTIME_BOUNDARY_CONSTRAINT_VIOLATION,
1299 non_targeted_path.as_slice(),
1300 ),
1301 Err(DiagnosticFactSchemaMismatch::InvalidSequence)
1302 );
1303 }
1304
1305 #[test]
1306 fn schema_enforces_global_ceiling_before_per_code_ceiling() {
1307 let facts = vec![(DiagnosticFactTag::ActualCount.raw(), 0); 81];
1308 assert_eq!(
1309 validate_raw_diagnostic_fact_schema(ErrorCode::QUERY_PLAN, facts.as_slice()),
1310 Err(DiagnosticFactSchemaMismatch::GlobalMaximumExceeded)
1311 );
1312 }
1313}