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 | 269 => 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 | 270 | 271 | 272 => 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 | 269 | 270
625 | 271 | 272 => 2,
626 3 | 20 => 5,
627 23 => 6,
628 196 | 234 => 4,
629 223 => 73,
630 225 => 9,
631 _ => 0,
632 }
633}
634
635#[expect(
636 clippy::too_many_lines,
637 reason = "the query-plan E-code deliberately owns several exact finite fact sequences"
638)]
639fn query_plan_schema(fact_count: usize, fact_at: &impl Fn(usize) -> (u8, u64)) -> bool {
640 fact_count == 0
641 || tags_match(fact_count, fact_at, &[DiagnosticFactTag::TermIndex])
642 || tags_match(fact_count, fact_at, &[DiagnosticFactTag::ComponentIndex])
643 || tags_match(fact_count, fact_at, &[DiagnosticFactTag::GroupIndex])
644 || tags_match(fact_count, fact_at, &[DiagnosticFactTag::ClauseIndex])
645 || tags_match(fact_count, fact_at, &[DiagnosticFactTag::AggregateIndex])
646 || tags_match(fact_count, fact_at, &[DiagnosticFactTag::AggregateKind])
647 || tags_match(fact_count, fact_at, &[DiagnosticFactTag::ProjectionIndex])
648 || tags_match(
649 fact_count,
650 fact_at,
651 &[
652 DiagnosticFactTag::FirstTermIndex,
653 DiagnosticFactTag::DuplicateTermIndex,
654 ],
655 )
656 || tags_match(
657 fact_count,
658 fact_at,
659 &[
660 DiagnosticFactTag::ClauseIndex,
661 DiagnosticFactTag::OperatorKind,
662 ],
663 )
664 || tags_match(
665 fact_count,
666 fact_at,
667 &[
668 DiagnosticFactTag::AggregateIndex,
669 DiagnosticFactTag::AggregateKind,
670 ],
671 )
672 || tags_match(
673 fact_count,
674 fact_at,
675 &[
676 DiagnosticFactTag::AggregateKind,
677 DiagnosticFactTag::TypeFamily,
678 ],
679 )
680 || tags_match(
681 fact_count,
682 fact_at,
683 &[
684 DiagnosticFactTag::OperatorKind,
685 DiagnosticFactTag::TypeFamily,
686 ],
687 )
688 || tags_match(
689 fact_count,
690 fact_at,
691 &[
692 DiagnosticFactTag::BranchIndex,
693 DiagnosticFactTag::TypeFamily,
694 ],
695 )
696 || tags_match(
697 fact_count,
698 fact_at,
699 &[DiagnosticFactTag::TypeFamily, DiagnosticFactTag::TypeFamily],
700 )
701 || tags_match(
702 fact_count,
703 fact_at,
704 &[
705 DiagnosticFactTag::ClauseIndex,
706 DiagnosticFactTag::AggregateIndex,
707 DiagnosticFactTag::ActualCount,
708 ],
709 )
710 || tags_match(
711 fact_count,
712 fact_at,
713 &[
714 DiagnosticFactTag::FunctionKind,
715 DiagnosticFactTag::ExpectedArity,
716 DiagnosticFactTag::ActualArity,
717 ],
718 )
719 || tags_match(
720 fact_count,
721 fact_at,
722 &[
723 DiagnosticFactTag::FunctionKind,
724 DiagnosticFactTag::ArgumentIndex,
725 DiagnosticFactTag::TypeFamily,
726 ],
727 )
728 || tags_match(
729 fact_count,
730 fact_at,
731 &[
732 DiagnosticFactTag::OperatorKind,
733 DiagnosticFactTag::TypeFamily,
734 DiagnosticFactTag::TypeFamily,
735 ],
736 )
737 || tags_match(
738 fact_count,
739 fact_at,
740 &[
741 DiagnosticFactTag::BranchIndex,
742 DiagnosticFactTag::TypeFamily,
743 DiagnosticFactTag::TypeFamily,
744 ],
745 )
746 || tags_match(
747 fact_count,
748 fact_at,
749 &[
750 DiagnosticFactTag::TypeFamily,
751 DiagnosticFactTag::BranchIndex,
752 DiagnosticFactTag::TypeFamily,
753 ],
754 )
755 || tags_match(
756 fact_count,
757 fact_at,
758 &[
759 DiagnosticFactTag::BranchIndex,
760 DiagnosticFactTag::TypeFamily,
761 DiagnosticFactTag::BranchIndex,
762 DiagnosticFactTag::TypeFamily,
763 ],
764 )
765 || tags_match(
766 fact_count,
767 fact_at,
768 &[
769 DiagnosticFactTag::FunctionKind,
770 DiagnosticFactTag::ArgumentIndex,
771 DiagnosticFactTag::TypeFamily,
772 DiagnosticFactTag::ArgumentIndex,
773 DiagnosticFactTag::TypeFamily,
774 ],
775 )
776}
777
778fn cursor_schema(fact_count: usize, fact_at: &impl Fn(usize) -> (u8, u64)) -> bool {
779 if fact_count == 0 {
780 return true;
781 }
782 if tags_match(fact_count, fact_at, &[DiagnosticFactTag::DecodeReason]) {
783 return matches!(fact_at(0).1, 1 | 3 | 5 | 6 | 7);
784 }
785 if tags_match(
786 fact_count,
787 fact_at,
788 &[
789 DiagnosticFactTag::ActualLength,
790 DiagnosticFactTag::Maximum,
791 DiagnosticFactTag::DecodeReason,
792 ],
793 ) {
794 return fact_at(2).1 == DiagnosticDecodeReason::CursorTooLong.raw();
795 }
796 if tags_match(
797 fact_count,
798 fact_at,
799 &[
800 DiagnosticFactTag::ComponentIndex,
801 DiagnosticFactTag::DecodeReason,
802 ],
803 ) {
804 return matches!(fact_at(1).1, 4 | 6 | 7);
805 }
806 tags_match(
807 fact_count,
808 fact_at,
809 &[
810 DiagnosticFactTag::ExpectedSignaturePrefix,
811 DiagnosticFactTag::ActualSignaturePrefix,
812 ],
813 ) || tags_match(
814 fact_count,
815 fact_at,
816 &[
817 DiagnosticFactTag::ExpectedOffset,
818 DiagnosticFactTag::ActualOffset,
819 ],
820 )
821}
822
823fn store_corruption_schema(fact_count: usize, fact_at: &impl Fn(usize) -> (u8, u64)) -> bool {
824 fact_count == 0
825 || (tags_match(
826 fact_count,
827 fact_at,
828 &[
829 DiagnosticFactTag::ComponentKind,
830 DiagnosticFactTag::ActualLength,
831 DiagnosticFactTag::Limit,
832 ],
833 ) && fact_at(0).1 == DiagnosticComponentKind::CommitDataKey.raw())
834 || tags_match(
835 fact_count,
836 fact_at,
837 &[
838 DiagnosticFactTag::ExpectedEntityTag,
839 DiagnosticFactTag::ActualEntityTag,
840 ],
841 )
842}
843
844fn runtime_corruption_schema(fact_count: usize, fact_at: &impl Fn(usize) -> (u8, u64)) -> bool {
845 store_corruption_schema(fact_count, fact_at)
846 || (tags_match(fact_count, fact_at, &[DiagnosticFactTag::DecodeReason])
847 && matches!(fact_at(0).1, 8..=10))
848}
849
850fn incompatible_format_schema(fact_count: usize, fact_at: &impl Fn(usize) -> (u8, u64)) -> bool {
851 fact_count == 0
852 || tags_match(fact_count, fact_at, &[DiagnosticFactTag::ExpectedVersion])
853 || tags_match(
854 fact_count,
855 fact_at,
856 &[
857 DiagnosticFactTag::ExpectedVersion,
858 DiagnosticFactTag::ActualVersion,
859 ],
860 )
861}
862
863fn runtime_invariant_schema(fact_count: usize, fact_at: &impl Fn(usize) -> (u8, u64)) -> bool {
864 fact_count == 0
865 || (tags_match(
866 fact_count,
867 fact_at,
868 &[
869 DiagnosticFactTag::EntityTag,
870 DiagnosticFactTag::PhysicalGeneration,
871 DiagnosticFactTag::ComponentKind,
872 DiagnosticFactTag::ActualArity,
873 DiagnosticFactTag::Maximum,
874 ],
875 ) && fact_at(2).1 == DiagnosticComponentKind::IndexKey.raw())
876}
877
878fn runtime_conflict_schema(fact_count: usize, fact_at: &impl Fn(usize) -> (u8, u64)) -> bool {
879 fact_count == 0
880 || tags_match(fact_count, fact_at, &[DiagnosticFactTag::ExpectedRevision])
881 || tags_match(
882 fact_count,
883 fact_at,
884 &[
885 DiagnosticFactTag::ExpectedRevision,
886 DiagnosticFactTag::CurrentRevision,
887 ],
888 )
889}
890
891fn runtime_unsupported_schema(fact_count: usize, fact_at: &impl Fn(usize) -> (u8, u64)) -> bool {
892 fact_count == 0
893 || (tags_match(
894 fact_count,
895 fact_at,
896 &[
897 DiagnosticFactTag::EntityTag,
898 DiagnosticFactTag::PhysicalGeneration,
899 DiagnosticFactTag::ComponentIndex,
900 DiagnosticFactTag::ComponentKind,
901 DiagnosticFactTag::ActualLength,
902 DiagnosticFactTag::Limit,
903 ],
904 ) && fact_at(3).1 == DiagnosticComponentKind::IndexKeyComponent.raw())
905}
906
907fn runtime_internal_schema(fact_count: usize, fact_at: &impl Fn(usize) -> (u8, u64)) -> bool {
908 fact_count == 0
909 || tags_match(
910 fact_count,
911 fact_at,
912 &[
913 DiagnosticFactTag::ExpectedMemoryId,
914 DiagnosticFactTag::ActualMemoryId,
915 ],
916 )
917 || (tags_match(
918 fact_count,
919 fact_at,
920 &[
921 DiagnosticFactTag::ComponentKind,
922 DiagnosticFactTag::ExpectedArity,
923 DiagnosticFactTag::ActualArity,
924 ],
925 ) && fact_at(0).1 == DiagnosticComponentKind::RelationTargetPrimaryKey.raw())
926}
927
928fn constraint_schema(
929 fact_count: usize,
930 fact_at: &impl Fn(usize) -> (u8, u64),
931 allow_targeted_path: bool,
932) -> bool {
933 const COMMON: &[DiagnosticFactTag] = &[
934 DiagnosticFactTag::AcceptedSchemaFingerprintMethod,
935 DiagnosticFactTag::AcceptedSchemaFingerprintHigh,
936 DiagnosticFactTag::AcceptedSchemaFingerprintLow,
937 DiagnosticFactTag::EntityTag,
938 DiagnosticFactTag::ConstraintId,
939 DiagnosticFactTag::ConstraintKind,
940 DiagnosticFactTag::ConstraintContext,
941 ];
942 if fact_count < COMMON.len()
943 || !tags_prefix_matches(fact_count, fact_at, COMMON)
944 || fact_at(6).1 != DiagnosticConstraintContext::WriteAdmission.raw()
945 {
946 return false;
947 }
948
949 let constraint_kind = fact_at(5).1;
950 if DiagnosticConstraintKind::known(constraint_kind).is_none() {
951 return false;
952 }
953 let mut index = COMMON.len();
954 if index < fact_count && fact_at(index).0 == DiagnosticFactTag::MutationOperation.raw() {
955 index += 1;
956 if index < fact_count && fact_at(index).0 == DiagnosticFactTag::BatchPosition.raw() {
957 index += 1;
958 }
959 }
960
961 let path_len = fact_count - index;
962 if path_len == 0 {
963 return !allow_targeted_path
964 || constraint_kind != DiagnosticConstraintKind::TargetedRule.raw();
965 }
966 allow_targeted_path
967 && constraint_kind == DiagnosticConstraintKind::TargetedRule.raw()
968 && path_len <= 64
969 && (index..fact_count).all(|position| {
970 DiagnosticFactTag::known(fact_at(position).0).is_some_and(is_value_path_tag)
971 })
972}
973
974fn tags_match(
975 fact_count: usize,
976 fact_at: &impl Fn(usize) -> (u8, u64),
977 expected: &[DiagnosticFactTag],
978) -> bool {
979 fact_count == expected.len() && tags_prefix_matches(fact_count, fact_at, expected)
980}
981
982fn tags_prefix_matches(
983 fact_count: usize,
984 fact_at: &impl Fn(usize) -> (u8, u64),
985 expected: &[DiagnosticFactTag],
986) -> bool {
987 fact_count >= expected.len()
988 && expected
989 .iter()
990 .enumerate()
991 .all(|(index, tag)| fact_at(index).0 == tag.raw())
992}
993
994const fn is_value_path_tag(tag: DiagnosticFactTag) -> bool {
995 matches!(
996 tag,
997 DiagnosticFactTag::RootField
998 | DiagnosticFactTag::RecordMember
999 | DiagnosticFactTag::TupleElement
1000 | DiagnosticFactTag::Newtype
1001 | DiagnosticFactTag::EnumVariant
1002 | DiagnosticFactTag::ListElement
1003 | DiagnosticFactTag::SetElement
1004 | DiagnosticFactTag::MapEntryKey
1005 | DiagnosticFactTag::MapEntryValue
1006 )
1007}
1008
1009const fn diagnostic_fact_value_is_valid(tag: DiagnosticFactTag, value: u64) -> bool {
1010 match tag {
1011 DiagnosticFactTag::AcceptedSchemaFingerprintMethod
1012 | DiagnosticFactTag::ExpectedMemoryId
1013 | DiagnosticFactTag::ActualMemoryId => value <= u8::MAX as u64,
1014 DiagnosticFactTag::ConstraintId
1015 | DiagnosticFactTag::FieldId
1016 | DiagnosticFactTag::IndexId
1017 | DiagnosticFactTag::RelationId
1018 | DiagnosticFactTag::BatchPosition
1019 | DiagnosticFactTag::FirstBatchPosition
1020 | DiagnosticFactTag::DuplicateBatchPosition
1021 | DiagnosticFactTag::RowLayout
1022 | DiagnosticFactTag::HistoryFloor
1023 | DiagnosticFactTag::CurrentLayout
1024 | DiagnosticFactTag::RootField
1025 | DiagnosticFactTag::Newtype
1026 | DiagnosticFactTag::ListElement
1027 | DiagnosticFactTag::SetElement
1028 | DiagnosticFactTag::MapEntryKey
1029 | DiagnosticFactTag::MapEntryValue => value <= u32::MAX as u64,
1030 DiagnosticFactTag::ConstraintKind => DiagnosticConstraintKind::known(value).is_some(),
1031 DiagnosticFactTag::ConstraintContext => DiagnosticConstraintContext::known(value).is_some(),
1032 DiagnosticFactTag::TypeFamily => DiagnosticTypeFamily::known(value).is_some(),
1033 DiagnosticFactTag::FunctionKind => DiagnosticFunctionKind::known(value).is_some(),
1034 DiagnosticFactTag::OperatorKind => DiagnosticOperatorKind::known(value).is_some(),
1035 DiagnosticFactTag::AggregateKind => DiagnosticAggregateKind::known(value).is_some(),
1036 DiagnosticFactTag::ComponentKind => DiagnosticComponentKind::known(value).is_some(),
1037 DiagnosticFactTag::DecodeReason => DiagnosticDecodeReason::known(value).is_some(),
1038 DiagnosticFactTag::MutationOperation => DiagnosticMutationOperation::known(value).is_some(),
1039 _ => true,
1040 }
1041}
1042
1043#[cfg(test)]
1044mod tests {
1045 use super::{
1046 DiagnosticAggregateKind, DiagnosticComponentKind, DiagnosticConstraintContext,
1047 DiagnosticConstraintKind, DiagnosticDecodeReason, DiagnosticFactSchemaMismatch,
1048 DiagnosticFactTag, DiagnosticFunctionKind, DiagnosticMutationOperation,
1049 DiagnosticOperatorKind, DiagnosticTypeFamily, ORDERED_FACT_TAGS, pack_u32_pair,
1050 unpack_u32_pair, validate_known_diagnostic_fact_schema,
1051 validate_raw_diagnostic_fact_schema,
1052 };
1053 use crate::ErrorCode;
1054
1055 #[test]
1056 fn fact_tag_registry_is_fixed_unique_and_contiguous() {
1057 for (index, tag) in ORDERED_FACT_TAGS.iter().copied().enumerate() {
1058 let expected = u8::try_from(index + 1).expect("fact-tag index fits u8");
1059 assert_eq!(tag.raw(), expected);
1060 assert_eq!(DiagnosticFactTag::known(expected), Some(tag));
1061 }
1062
1063 assert_eq!(DiagnosticFactTag::known(0), None);
1064 assert_eq!(DiagnosticFactTag::known(90), None);
1065 assert_eq!(DiagnosticFactTag::known(u8::MAX), None);
1066 }
1067
1068 #[test]
1069 fn accepted_identity_pair_packing_is_exact() {
1070 for pair in [
1071 (0, 0),
1072 (1, 2),
1073 (u32::MAX, 0),
1074 (0, u32::MAX),
1075 (u32::MAX, u32::MAX),
1076 ] {
1077 assert_eq!(unpack_u32_pair(pack_u32_pair(pair.0, pair.1)), pair);
1078 }
1079 }
1080
1081 #[test]
1082 fn constraint_fact_value_registries_are_fixed() {
1083 assert_eq!(DiagnosticConstraintKind::Check.raw(), 1);
1084 assert_eq!(DiagnosticConstraintKind::NotNull.raw(), 2);
1085 assert_eq!(DiagnosticConstraintKind::Relation.raw(), 3);
1086 assert_eq!(DiagnosticConstraintKind::TargetedRule.raw(), 4);
1087 assert_eq!(DiagnosticConstraintKind::Unique.raw(), 5);
1088 assert_eq!(DiagnosticConstraintKind::known(0), None);
1089 assert_eq!(DiagnosticConstraintKind::known(6), None);
1090
1091 assert_eq!(DiagnosticConstraintContext::Integrity.raw(), 1);
1092 assert_eq!(DiagnosticConstraintContext::MigrationValidation.raw(), 2);
1093 assert_eq!(DiagnosticConstraintContext::WriteAdmission.raw(), 3);
1094 assert_eq!(DiagnosticConstraintContext::known(0), None);
1095 assert_eq!(DiagnosticConstraintContext::known(4), None);
1096 }
1097
1098 #[test]
1099 fn component_kind_registry_is_fixed_and_numeric() {
1100 let kinds = [
1101 DiagnosticComponentKind::CommitDataKey,
1102 DiagnosticComponentKind::IndexKey,
1103 DiagnosticComponentKind::IndexKeyComponent,
1104 DiagnosticComponentKind::RelationTargetPrimaryKey,
1105 ];
1106
1107 for (index, kind) in kinds.iter().copied().enumerate() {
1108 let expected = (index + 1) as u64;
1109 assert_eq!(kind.raw(), expected);
1110 assert_eq!(DiagnosticComponentKind::known(expected), Some(kind));
1111 assert_eq!(format!("{kind:?}"), expected.to_string());
1112 }
1113 assert_eq!(DiagnosticComponentKind::known(0), None);
1114 assert_eq!(DiagnosticComponentKind::known(5), None);
1115 }
1116
1117 #[test]
1118 fn decode_reason_registry_is_fixed_and_numeric() {
1119 let reasons = [
1120 DiagnosticDecodeReason::CursorEmpty,
1121 DiagnosticDecodeReason::CursorTooLong,
1122 DiagnosticDecodeReason::CursorOddLength,
1123 DiagnosticDecodeReason::CursorInvalidHex,
1124 DiagnosticDecodeReason::CursorGroupedDirectionMismatch,
1125 DiagnosticDecodeReason::CursorTokenEncode,
1126 DiagnosticDecodeReason::CursorTokenDecode,
1127 DiagnosticDecodeReason::RecoveryMarkerMagic,
1128 DiagnosticDecodeReason::RecoveryMarkerChecksum,
1129 DiagnosticDecodeReason::RecoveryMarkerState,
1130 ];
1131
1132 for (index, reason) in reasons.iter().copied().enumerate() {
1133 let expected = (index + 1) as u64;
1134 assert_eq!(reason.raw(), expected);
1135 assert_eq!(DiagnosticDecodeReason::known(expected), Some(reason));
1136 assert_eq!(format!("{reason:?}"), expected.to_string());
1137 }
1138
1139 assert_eq!(DiagnosticDecodeReason::known(0), None);
1140 assert_eq!(DiagnosticDecodeReason::known(11), None);
1141 }
1142
1143 #[test]
1144 fn mutation_operation_registry_is_fixed_and_numeric() {
1145 let operations = [
1146 DiagnosticMutationOperation::Insert,
1147 DiagnosticMutationOperation::Replace,
1148 DiagnosticMutationOperation::Update,
1149 DiagnosticMutationOperation::Delete,
1150 ];
1151
1152 for (index, operation) in operations.iter().copied().enumerate() {
1153 let expected = (index + 1) as u64;
1154 assert_eq!(operation.raw(), expected);
1155 assert_eq!(
1156 DiagnosticMutationOperation::known(expected),
1157 Some(operation)
1158 );
1159 assert_eq!(format!("{operation:?}"), expected.to_string());
1160 }
1161
1162 assert_eq!(DiagnosticMutationOperation::known(0), None);
1163 assert_eq!(DiagnosticMutationOperation::known(5), None);
1164 }
1165
1166 #[test]
1167 fn query_kind_registries_are_fixed_contiguous_and_numeric() {
1168 for raw in 1..=9 {
1169 let value = DiagnosticTypeFamily::known(raw).expect("type family should be known");
1170 assert_eq!(value.raw(), raw);
1171 assert_eq!(format!("{value:?}"), raw.to_string());
1172 }
1173 assert_eq!(DiagnosticTypeFamily::known(0), None);
1174 assert_eq!(DiagnosticTypeFamily::known(10), None);
1175
1176 for raw in 1..=39 {
1177 let value = DiagnosticFunctionKind::known(raw).expect("function kind should be known");
1178 assert_eq!(value.raw(), raw);
1179 assert_eq!(format!("{value:?}"), raw.to_string());
1180 }
1181 assert_eq!(DiagnosticFunctionKind::known(0), None);
1182 assert_eq!(DiagnosticFunctionKind::known(40), None);
1183
1184 for raw in 1..=18 {
1185 let value = DiagnosticOperatorKind::known(raw).expect("operator kind should be known");
1186 assert_eq!(value.raw(), raw);
1187 assert_eq!(format!("{value:?}"), raw.to_string());
1188 }
1189 assert_eq!(DiagnosticOperatorKind::known(0), None);
1190 assert_eq!(DiagnosticOperatorKind::known(19), None);
1191
1192 for raw in 1..=8 {
1193 let value =
1194 DiagnosticAggregateKind::known(raw).expect("aggregate kind should be known");
1195 assert_eq!(value.raw(), raw);
1196 assert_eq!(format!("{value:?}"), raw.to_string());
1197 }
1198 assert_eq!(DiagnosticAggregateKind::known(0), None);
1199 assert_eq!(DiagnosticAggregateKind::known(9), None);
1200 }
1201
1202 #[test]
1203 fn per_code_schema_rejects_missing_disallowed_and_noncanonical_tags() {
1204 let valid = [
1205 (DiagnosticFactTag::ActualCount, 5),
1206 (DiagnosticFactTag::Limit, 4),
1207 ];
1208 assert_eq!(
1209 validate_known_diagnostic_fact_schema(
1210 ErrorCode::RUNTIME_BOUNDARY_MUTATION_BATCH_TOO_MANY_ITEMS,
1211 &valid,
1212 ),
1213 Ok(())
1214 );
1215 assert_eq!(
1216 validate_known_diagnostic_fact_schema(
1217 ErrorCode::RUNTIME_BOUNDARY_MUTATION_BATCH_TOO_MANY_ITEMS,
1218 &valid[..1],
1219 ),
1220 Err(DiagnosticFactSchemaMismatch::InvalidSequence)
1221 );
1222 assert_eq!(
1223 validate_known_diagnostic_fact_schema(
1224 ErrorCode::RUNTIME_BOUNDARY_MUTATION_BATCH_TOO_MANY_ITEMS,
1225 &[valid[1], valid[0]],
1226 ),
1227 Err(DiagnosticFactSchemaMismatch::InvalidSequence)
1228 );
1229 assert_eq!(
1230 validate_known_diagnostic_fact_schema(ErrorCode::QUERY_VALIDATE, &valid),
1231 Err(DiagnosticFactSchemaMismatch::CodeMaximumExceeded)
1232 );
1233 }
1234
1235 #[test]
1236 fn raw_schema_keeps_unknown_context_numeric_but_marks_it_invalid() {
1237 assert_eq!(
1238 validate_raw_diagnostic_fact_schema(
1239 ErrorCode::QUERY_INVALID_CONTINUATION_CURSOR,
1240 &[(u8::MAX, 17)],
1241 ),
1242 Err(DiagnosticFactSchemaMismatch::InvalidSequence)
1243 );
1244 assert_eq!(
1245 validate_raw_diagnostic_fact_schema(
1246 ErrorCode::QUERY_INVALID_CONTINUATION_CURSOR,
1247 &[(DiagnosticFactTag::DecodeReason.raw(), u64::MAX)],
1248 ),
1249 Err(DiagnosticFactSchemaMismatch::InvalidSequence)
1250 );
1251 }
1252
1253 #[test]
1254 fn constraint_schema_enforces_authority_operation_and_bounded_path_suffix() {
1255 let mut targeted = vec![
1256 (DiagnosticFactTag::AcceptedSchemaFingerprintMethod, 1),
1257 (DiagnosticFactTag::AcceptedSchemaFingerprintHigh, 2),
1258 (DiagnosticFactTag::AcceptedSchemaFingerprintLow, 3),
1259 (DiagnosticFactTag::EntityTag, 17),
1260 (DiagnosticFactTag::ConstraintId, 4),
1261 (
1262 DiagnosticFactTag::ConstraintKind,
1263 DiagnosticConstraintKind::TargetedRule.raw(),
1264 ),
1265 (
1266 DiagnosticFactTag::ConstraintContext,
1267 DiagnosticConstraintContext::WriteAdmission.raw(),
1268 ),
1269 (
1270 DiagnosticFactTag::MutationOperation,
1271 DiagnosticMutationOperation::Insert.raw(),
1272 ),
1273 (DiagnosticFactTag::BatchPosition, 0),
1274 ];
1275 targeted.extend((0..64).map(|index| (DiagnosticFactTag::ListElement, index)));
1276 assert_eq!(targeted.len(), 73);
1277 assert_eq!(
1278 validate_known_diagnostic_fact_schema(
1279 ErrorCode::RUNTIME_BOUNDARY_CONSTRAINT_VIOLATION,
1280 targeted.as_slice(),
1281 ),
1282 Ok(())
1283 );
1284
1285 let mut overlong = targeted.clone();
1286 overlong.push((DiagnosticFactTag::ListElement, 64));
1287 assert_eq!(
1288 validate_known_diagnostic_fact_schema(
1289 ErrorCode::RUNTIME_BOUNDARY_CONSTRAINT_VIOLATION,
1290 overlong.as_slice(),
1291 ),
1292 Err(DiagnosticFactSchemaMismatch::CodeMaximumExceeded)
1293 );
1294
1295 let mut non_targeted_path = targeted;
1296 non_targeted_path[5].1 = DiagnosticConstraintKind::Unique.raw();
1297 assert_eq!(
1298 validate_known_diagnostic_fact_schema(
1299 ErrorCode::RUNTIME_BOUNDARY_CONSTRAINT_VIOLATION,
1300 non_targeted_path.as_slice(),
1301 ),
1302 Err(DiagnosticFactSchemaMismatch::InvalidSequence)
1303 );
1304 }
1305
1306 #[test]
1307 fn schema_enforces_global_ceiling_before_per_code_ceiling() {
1308 let facts = vec![(DiagnosticFactTag::ActualCount.raw(), 0); 81];
1309 assert_eq!(
1310 validate_raw_diagnostic_fact_schema(ErrorCode::QUERY_PLAN, facts.as_slice()),
1311 Err(DiagnosticFactSchemaMismatch::GlobalMaximumExceeded)
1312 );
1313 }
1314}