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 ExecutionBudgetScope = 90;
144 ExecutionLane = 91;
145 QueryShapeFingerprintPrefix = 92;
146 BacklogResource = 93;
147 CurrentCount = 94;
148 ProposedCount = 95;
149 GroupedLimit = 96;
150}
151
152#[derive(Clone, Copy, Eq, Hash, PartialEq)]
157pub enum DiagnosticDecodeReason {
158 CursorEmpty,
159 CursorTooLong,
160 CursorInvalidLength,
161 CursorInvalidBase64,
162 CursorGroupedDirectionMismatch,
163 CursorTokenEncode,
164 CursorTokenDecode,
165 RecoveryMarkerMagic,
166 RecoveryMarkerChecksum,
167 RecoveryMarkerState,
168 CursorGroupedContinuationRequiresLimit,
169 CursorGlobalDistinctContinuationUnsupported,
170}
171
172#[derive(Clone, Copy, Eq, Hash, PartialEq)]
174pub enum DiagnosticMutationOperation {
175 Insert,
176 Replace,
177 Update,
178 Delete,
179}
180
181macro_rules! define_numeric_fact_value_registry {
182 (
183 $(#[$enum_meta:meta])*
184 pub enum $name:ident {
185 $($variant:ident = $raw:literal;)+
186 }
187 ) => {
188 $(#[$enum_meta])*
189 #[derive(Clone, Copy, Eq, Hash, PartialEq)]
190 pub enum $name {
191 $(
192 #[doc = concat!("Compact diagnostic value ", stringify!($raw), ".")]
193 $variant,
194 )+
195 }
196
197 impl $name {
198 #[must_use]
200 pub const fn raw(self) -> u64 {
201 match self {
202 $(Self::$variant => $raw,)+
203 }
204 }
205
206 #[must_use]
208 pub const fn known(raw: u64) -> Option<Self> {
209 match raw {
210 $($raw => Some(Self::$variant),)+
211 _ => None,
212 }
213 }
214 }
215
216 impl fmt::Debug for $name {
217 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
218 write!(f, "{}", self.raw())
219 }
220 }
221 };
222}
223
224define_numeric_fact_value_registry! {
225 pub enum DiagnosticBacklogResource {
227 Batches = 1;
228 Records = 2;
229 EncodedBytes = 3;
230 }
231}
232
233define_numeric_fact_value_registry! {
234 pub enum DiagnosticGroupBudgetResource {
236 DistinctValuesPerGroup = 1;
237 DistinctValuesTotal = 2;
238 EstimatedBytes = 3;
239 Groups = 4;
240 }
241}
242
243impl DiagnosticGroupBudgetResource {
244 #[must_use]
246 pub const fn execution_resource(self) -> DiagnosticExecutionBudgetResource {
247 match self {
248 Self::DistinctValuesPerGroup | Self::DistinctValuesTotal | Self::Groups => {
249 DiagnosticExecutionBudgetResource::GroupDistinctEntries
250 }
251 Self::EstimatedBytes => DiagnosticExecutionBudgetResource::GroupDistinctStateBytes,
252 }
253 }
254}
255
256define_numeric_fact_value_registry! {
257 pub enum DiagnosticExecutionBudgetResource {
259 QueryExecutions = 1;
260 PlanningSteps = 2;
261 PlanCompilations = 3;
262 KeyIndexEntriesVisited = 4;
263 RowsVisited = 5;
264 StoredBytesRead = 6;
265 PredicateExpressionSteps = 7;
266 NestedValueSteps = 8;
267 DecodedBytes = 9;
268 MaterializedBytes = 10;
269 SortEntries = 11;
270 SortComparisons = 12;
271 SortTemporaryBytes = 13;
272 GroupDistinctEntries = 14;
273 GroupDistinctStateBytes = 15;
274 CursorSteps = 16;
275 TemporaryBytes = 17;
276 ResultRows = 18;
277 ResultBytes = 19;
278 InstructionUnits = 20;
279 }
280}
281
282impl DiagnosticExecutionBudgetResource {
283 pub const ALL: [Self; 20] = [
285 Self::QueryExecutions,
286 Self::PlanningSteps,
287 Self::PlanCompilations,
288 Self::KeyIndexEntriesVisited,
289 Self::RowsVisited,
290 Self::StoredBytesRead,
291 Self::PredicateExpressionSteps,
292 Self::NestedValueSteps,
293 Self::DecodedBytes,
294 Self::MaterializedBytes,
295 Self::SortEntries,
296 Self::SortComparisons,
297 Self::SortTemporaryBytes,
298 Self::GroupDistinctEntries,
299 Self::GroupDistinctStateBytes,
300 Self::CursorSteps,
301 Self::TemporaryBytes,
302 Self::ResultRows,
303 Self::ResultBytes,
304 Self::InstructionUnits,
305 ];
306}
307
308define_numeric_fact_value_registry! {
309 pub enum DiagnosticExecutionBudgetScope {
311 Execution = 1;
312 Request = 2;
313 }
314}
315
316define_numeric_fact_value_registry! {
317 pub enum DiagnosticExecutionLane {
319 PublicRead = 1;
320 TrustedRead = 2;
321 Diagnostic = 3;
322 Mutation = 4;
323 Recovery = 5;
324 }
325}
326
327define_numeric_fact_value_registry! {
328 pub enum DiagnosticConstraintKind {
330 Check = 1;
331 NotNull = 2;
332 Relation = 3;
333 TargetedRule = 4;
334 Unique = 5;
335 }
336}
337
338define_numeric_fact_value_registry! {
339 pub enum DiagnosticConstraintContext {
341 Integrity = 1;
342 MigrationValidation = 2;
343 WriteAdmission = 3;
344 }
345}
346
347define_numeric_fact_value_registry! {
348 pub enum DiagnosticComponentKind {
350 CommitDataKey = 1;
351 IndexKey = 2;
352 IndexKeyComponent = 3;
353 RelationTargetPrimaryKey = 4;
354 }
355}
356
357define_numeric_fact_value_registry! {
358 pub enum DiagnosticTypeFamily {
360 Blob = 1;
361 Bool = 2;
362 Collection = 3;
363 Null = 4;
364 Numeric = 5;
365 Opaque = 6;
366 Structured = 7;
367 Text = 8;
368 Unknown = 9;
369 }
370}
371
372define_numeric_fact_value_registry! {
373 pub enum DiagnosticFunctionKind {
375 Abs = 1;
376 Cbrt = 2;
377 Ceiling = 3;
378 Coalesce = 4;
379 CollectionContains = 5;
380 Contains = 6;
381 EndsWith = 7;
382 Exp = 8;
383 Floor = 9;
384 IsEmpty = 10;
385 IsMissing = 11;
386 IsNotEmpty = 12;
387 IsNotNull = 13;
388 IsNull = 14;
389 Left = 15;
390 Length = 16;
391 Ln = 17;
392 Log = 18;
393 Log2 = 19;
394 Log10 = 20;
395 Lower = 21;
396 Ltrim = 22;
397 Mod = 23;
398 NullIf = 24;
399 OctetLength = 25;
400 Position = 26;
401 Power = 27;
402 Replace = 28;
403 Right = 29;
404 Round = 30;
405 Rtrim = 31;
406 Sign = 32;
407 Sqrt = 33;
408 StartsWith = 34;
409 Substring = 35;
410 Trim = 36;
411 Trunc = 37;
412 Upper = 38;
413 InList = 39;
414 }
415}
416
417define_numeric_fact_value_registry! {
418 pub enum DiagnosticOperatorKind {
420 Not = 1;
421 Add = 2;
422 And = 3;
423 Div = 4;
424 Eq = 5;
425 Gt = 6;
426 Gte = 7;
427 Lt = 8;
428 Lte = 9;
429 Mul = 10;
430 Ne = 11;
431 Or = 12;
432 Sub = 13;
433 In = 14;
434 NotIn = 15;
435 Contains = 16;
436 StartsWith = 17;
437 EndsWith = 18;
438 }
439}
440
441define_numeric_fact_value_registry! {
442 pub enum DiagnosticAggregateKind {
444 Count = 1;
445 Sum = 2;
446 Avg = 3;
447 Exists = 4;
448 Min = 5;
449 Max = 6;
450 First = 7;
451 Last = 8;
452 }
453}
454
455impl DiagnosticDecodeReason {
456 #[must_use]
458 pub const fn raw(self) -> u64 {
459 match self {
460 Self::CursorEmpty => 1,
461 Self::CursorTooLong => 2,
462 Self::CursorInvalidLength => 3,
463 Self::CursorInvalidBase64 => 4,
464 Self::CursorGroupedDirectionMismatch => 5,
465 Self::CursorTokenEncode => 6,
466 Self::CursorTokenDecode => 7,
467 Self::RecoveryMarkerMagic => 8,
468 Self::RecoveryMarkerChecksum => 9,
469 Self::RecoveryMarkerState => 10,
470 Self::CursorGroupedContinuationRequiresLimit => 11,
471 Self::CursorGlobalDistinctContinuationUnsupported => 12,
472 }
473 }
474
475 #[must_use]
477 pub const fn known(raw: u64) -> Option<Self> {
478 match raw {
479 1 => Some(Self::CursorEmpty),
480 2 => Some(Self::CursorTooLong),
481 3 => Some(Self::CursorInvalidLength),
482 4 => Some(Self::CursorInvalidBase64),
483 5 => Some(Self::CursorGroupedDirectionMismatch),
484 6 => Some(Self::CursorTokenEncode),
485 7 => Some(Self::CursorTokenDecode),
486 8 => Some(Self::RecoveryMarkerMagic),
487 9 => Some(Self::RecoveryMarkerChecksum),
488 10 => Some(Self::RecoveryMarkerState),
489 11 => Some(Self::CursorGroupedContinuationRequiresLimit),
490 12 => Some(Self::CursorGlobalDistinctContinuationUnsupported),
491 _ => None,
492 }
493 }
494}
495
496impl DiagnosticMutationOperation {
497 #[must_use]
499 pub const fn raw(self) -> u64 {
500 match self {
501 Self::Insert => 1,
502 Self::Replace => 2,
503 Self::Update => 3,
504 Self::Delete => 4,
505 }
506 }
507
508 #[must_use]
510 pub const fn known(raw: u64) -> Option<Self> {
511 match raw {
512 1 => Some(Self::Insert),
513 2 => Some(Self::Replace),
514 3 => Some(Self::Update),
515 4 => Some(Self::Delete),
516 _ => None,
517 }
518 }
519}
520
521impl fmt::Debug for DiagnosticFactTag {
522 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
523 write!(f, "{}", self.raw())
524 }
525}
526
527impl fmt::Debug for DiagnosticDecodeReason {
528 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
529 write!(f, "{}", self.raw())
530 }
531}
532
533impl fmt::Debug for DiagnosticMutationOperation {
534 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
535 write!(f, "{}", self.raw())
536 }
537}
538
539#[must_use]
541pub const fn pack_u32_pair(high: u32, low: u32) -> u64 {
542 (high as u64) << 32 | low as u64
543}
544
545#[must_use]
547pub const fn unpack_u32_pair(value: u64) -> (u32, u32) {
548 let bytes = value.to_be_bytes();
549 (
550 u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]),
551 u32::from_be_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]),
552 )
553}
554
555#[derive(Clone, Copy, Debug, Eq, PartialEq)]
559pub enum DiagnosticFactSchemaMismatch {
560 GlobalMaximumExceeded,
562 CodeMaximumExceeded,
564 InvalidSequence,
566 InvalidValue,
568}
569
570pub fn validate_known_diagnostic_fact_schema(
575 code: ErrorCode,
576 facts: &[(DiagnosticFactTag, u64)],
577) -> Result<(), DiagnosticFactSchemaMismatch> {
578 validate_diagnostic_fact_schema(code, facts.len(), |index| {
579 let (tag, value) = facts[index];
580 (tag.raw(), value)
581 })
582}
583
584pub fn validate_raw_diagnostic_fact_schema(
589 code: ErrorCode,
590 facts: &[(u8, u64)],
591) -> Result<(), DiagnosticFactSchemaMismatch> {
592 validate_diagnostic_fact_schema(code, facts.len(), |index| facts[index])
593}
594
595#[expect(
596 clippy::too_many_lines,
597 reason = "the frozen E-code schema registry keeps every numeric owner visible in one exhaustive dispatch"
598)]
599fn validate_diagnostic_fact_schema(
600 code: ErrorCode,
601 fact_count: usize,
602 fact_at: impl Fn(usize) -> (u8, u64),
603) -> Result<(), DiagnosticFactSchemaMismatch> {
604 if fact_count > MAX_PUBLIC_DIAGNOSTIC_FACTS {
605 return Err(DiagnosticFactSchemaMismatch::GlobalMaximumExceeded);
606 }
607
608 let maximum = diagnostic_fact_maximum(code);
609 if fact_count > maximum {
610 return Err(DiagnosticFactSchemaMismatch::CodeMaximumExceeded);
611 }
612
613 let valid_sequence = match code.raw() {
614 3 => query_plan_schema(fact_count, &fact_at),
615 5 => cursor_schema(fact_count, &fact_at),
616 15 => store_corruption_schema(fact_count, &fact_at),
617 17 => runtime_corruption_schema(fact_count, &fact_at),
618 18 => incompatible_format_schema(fact_count, &fact_at),
619 19 => runtime_invariant_schema(fact_count, &fact_at),
620 20 => runtime_conflict_schema(fact_count, &fact_at),
621 22 => runtime_unsupported_schema(fact_count, &fact_at),
622 23 => runtime_internal_schema(fact_count, &fact_at),
623 130 => tags_match(
624 fact_count,
625 &fact_at,
626 &[
627 DiagnosticFactTag::ExpectedArity,
628 DiagnosticFactTag::ActualArity,
629 ],
630 ),
631 133 | 134 | 158 | 159 => {
632 tags_match(fact_count, &fact_at, &[DiagnosticFactTag::ProjectionIndex])
633 }
634 164 => tags_match(fact_count, &fact_at, &[DiagnosticFactTag::ParameterIndex]),
635 166 | 224 => {
636 tags_match(fact_count, &fact_at, &[DiagnosticFactTag::Limit])
637 || tags_match(
638 fact_count,
639 &fact_at,
640 &[DiagnosticFactTag::ActualLength, DiagnosticFactTag::Limit],
641 )
642 }
643 167 | 169 | 191 | 192 | 194 | 223 | 248 | 264 => tags_match(
644 fact_count,
645 &fact_at,
646 &[DiagnosticFactTag::ActualCount, DiagnosticFactTag::Limit],
647 ),
648 262 => {
649 tags_match(fact_count, &fact_at, &[DiagnosticFactTag::Limit])
650 || tags_match(
651 fact_count,
652 &fact_at,
653 &[DiagnosticFactTag::ActualCount, DiagnosticFactTag::Limit],
654 )
655 }
656 263 => tags_match(
657 fact_count,
658 &fact_at,
659 &[
660 DiagnosticFactTag::BacklogResource,
661 DiagnosticFactTag::CurrentCount,
662 DiagnosticFactTag::ProposedCount,
663 DiagnosticFactTag::Limit,
664 ],
665 ),
666 185 | 221 => mutation_schema(
667 fact_count,
668 &fact_at,
669 &[
670 DiagnosticFactTag::AcceptedSchemaFingerprintMethod,
671 DiagnosticFactTag::AcceptedSchemaFingerprintHigh,
672 DiagnosticFactTag::AcceptedSchemaFingerprintLow,
673 DiagnosticFactTag::EntityTag,
674 DiagnosticFactTag::FieldId,
675 DiagnosticFactTag::MutationOperation,
676 ],
677 ),
678 186 => tags_match(
679 fact_count,
680 &fact_at,
681 &[
682 DiagnosticFactTag::RowLayout,
683 DiagnosticFactTag::HistoryFloor,
684 DiagnosticFactTag::CurrentLayout,
685 ],
686 ),
687 187 => tags_match(
688 fact_count,
689 &fact_at,
690 &[
691 DiagnosticFactTag::RowLayout,
692 DiagnosticFactTag::ExpectedSlotCount,
693 DiagnosticFactTag::ActualSlotCount,
694 ],
695 ),
696 181 => {
697 fact_count == 0
698 || tags_match(
699 fact_count,
700 &fact_at,
701 &[DiagnosticFactTag::Limit, DiagnosticFactTag::ActualCount],
702 )
703 || tags_match(
704 fact_count,
705 &fact_at,
706 &[DiagnosticFactTag::Limit, DiagnosticFactTag::ActualLength],
707 )
708 }
709 190 => tags_match(
710 fact_count,
711 &fact_at,
712 &[DiagnosticFactTag::ActualCount, DiagnosticFactTag::Minimum],
713 ),
714 210 => constraint_schema(fact_count, &fact_at, true),
715 212 => constraint_schema(fact_count, &fact_at, false),
716 220 => mutation_schema(
717 fact_count,
718 &fact_at,
719 &[
720 DiagnosticFactTag::AcceptedSchemaFingerprintMethod,
721 DiagnosticFactTag::AcceptedSchemaFingerprintHigh,
722 DiagnosticFactTag::AcceptedSchemaFingerprintLow,
723 DiagnosticFactTag::EntityTag,
724 DiagnosticFactTag::MutationOperation,
725 ],
726 ),
727 222 => {
728 tags_match(fact_count, &fact_at, &[DiagnosticFactTag::ActualCount]) && fact_at(0).1 == 0
729 }
730 225 | 249 | 250 | 251 => tags_match(
731 fact_count,
732 &fact_at,
733 &[DiagnosticFactTag::ActualLength, DiagnosticFactTag::Limit],
734 ),
735 252 => {
736 let base = [
737 DiagnosticFactTag::BudgetResource,
738 DiagnosticFactTag::Limit,
739 DiagnosticFactTag::Actual,
740 DiagnosticFactTag::ExecutionBudgetScope,
741 DiagnosticFactTag::ExecutionLane,
742 DiagnosticFactTag::QueryShapeFingerprintPrefix,
743 ];
744 (fact_count == 6 || fact_count == 7)
745 && tags_match(6, &fact_at, &base)
746 && (fact_count == 6
747 || (fact_at(6).0 == DiagnosticFactTag::GroupedLimit.raw()
748 && DiagnosticGroupBudgetResource::known(fact_at(6).1).is_some_and(
749 |resource| resource.execution_resource().raw() == fact_at(0).1,
750 )))
751 }
752 253 => tags_match(
753 fact_count,
754 &fact_at,
755 &[
756 DiagnosticFactTag::BudgetResource,
757 DiagnosticFactTag::Limit,
758 DiagnosticFactTag::Actual,
759 ],
760 ),
761 271 | 272 => tags_match(
762 fact_count,
763 &fact_at,
764 &[DiagnosticFactTag::Limit, DiagnosticFactTag::Actual],
765 ),
766 274 => {
767 tags_match(
768 fact_count,
769 &fact_at,
770 &[DiagnosticFactTag::Expected, DiagnosticFactTag::Actual],
771 ) && (0..2).all(|index| (1..=u64::from(u16::MAX)).contains(&fact_at(index).1))
772 && fact_at(0).1 != fact_at(1).1
773 }
774 276 | 281 => {
775 fact_count == 0
776 || (tags_match(fact_count, &fact_at, &[DiagnosticFactTag::ActualMemoryId])
777 && fact_at(0).1 <= 254)
778 }
779 278 => {
780 tags_match(fact_count, &fact_at, &[DiagnosticFactTag::ExpectedCount])
781 && matches!(fact_at(0).1, 3 | 4)
782 }
783 280 => {
784 fact_count == 0
785 || (tags_match(
786 fact_count,
787 &fact_at,
788 &[
789 DiagnosticFactTag::ExpectedMemoryId,
790 DiagnosticFactTag::ActualMemoryId,
791 ],
792 ) && (0..2).all(|index| fact_at(index).1 <= 254)
793 && fact_at(0).1 != fact_at(1).1)
794 }
795 226 => tags_match(
796 fact_count,
797 &fact_at,
798 &[
799 DiagnosticFactTag::BatchPosition,
800 DiagnosticFactTag::ExpectedEntityTag,
801 DiagnosticFactTag::ActualEntityTag,
802 ],
803 ),
804 227 => tags_match(
805 fact_count,
806 &fact_at,
807 &[
808 DiagnosticFactTag::EntityTag,
809 DiagnosticFactTag::FirstBatchPosition,
810 DiagnosticFactTag::DuplicateBatchPosition,
811 ],
812 ),
813 _ => fact_count == 0,
814 };
815 if !valid_sequence {
816 return Err(DiagnosticFactSchemaMismatch::InvalidSequence);
817 }
818
819 for index in 0..fact_count {
820 let (raw_tag, value) = fact_at(index);
821 let Some(tag) = DiagnosticFactTag::known(raw_tag) else {
822 return Err(DiagnosticFactSchemaMismatch::InvalidSequence);
823 };
824 if !diagnostic_fact_value_is_valid(tag, value) {
825 return Err(DiagnosticFactSchemaMismatch::InvalidValue);
826 }
827 }
828 Ok(())
829}
830
831const fn diagnostic_fact_maximum(code: ErrorCode) -> usize {
832 match code.raw() {
833 5 | 15 | 17 | 186 | 187 | 226 | 227 | 253 => 3,
834 133 | 134 | 158 | 159 | 164 | 222 | 276 | 278 | 281 => 1,
835 18 | 20 | 130 | 166 | 167 | 169 | 181 | 190 | 191 | 192 | 194 | 223 | 224 | 225 | 248
836 | 249 | 250 | 251 | 262 | 264 | 271 | 272 | 274 | 280 => 2,
837 3 | 19 | 23 => 5,
838 22 | 220 => 6,
839 185 | 221 | 252 => 7,
840 263 => 4,
841 210 => 73,
842 212 => 9,
843 _ => 0,
844 }
845}
846
847#[expect(
848 clippy::too_many_lines,
849 reason = "the query-plan E-code deliberately owns several exact finite fact sequences"
850)]
851fn query_plan_schema(fact_count: usize, fact_at: &impl Fn(usize) -> (u8, u64)) -> bool {
852 fact_count == 0
853 || tags_match(fact_count, fact_at, &[DiagnosticFactTag::TermIndex])
854 || tags_match(fact_count, fact_at, &[DiagnosticFactTag::ComponentIndex])
855 || tags_match(fact_count, fact_at, &[DiagnosticFactTag::GroupIndex])
856 || tags_match(fact_count, fact_at, &[DiagnosticFactTag::ClauseIndex])
857 || tags_match(fact_count, fact_at, &[DiagnosticFactTag::AggregateIndex])
858 || tags_match(fact_count, fact_at, &[DiagnosticFactTag::AggregateKind])
859 || tags_match(fact_count, fact_at, &[DiagnosticFactTag::ProjectionIndex])
860 || tags_match(
861 fact_count,
862 fact_at,
863 &[
864 DiagnosticFactTag::FirstTermIndex,
865 DiagnosticFactTag::DuplicateTermIndex,
866 ],
867 )
868 || tags_match(
869 fact_count,
870 fact_at,
871 &[
872 DiagnosticFactTag::ClauseIndex,
873 DiagnosticFactTag::OperatorKind,
874 ],
875 )
876 || tags_match(
877 fact_count,
878 fact_at,
879 &[
880 DiagnosticFactTag::AggregateIndex,
881 DiagnosticFactTag::AggregateKind,
882 ],
883 )
884 || tags_match(
885 fact_count,
886 fact_at,
887 &[
888 DiagnosticFactTag::AggregateKind,
889 DiagnosticFactTag::TypeFamily,
890 ],
891 )
892 || tags_match(
893 fact_count,
894 fact_at,
895 &[
896 DiagnosticFactTag::OperatorKind,
897 DiagnosticFactTag::TypeFamily,
898 ],
899 )
900 || tags_match(
901 fact_count,
902 fact_at,
903 &[
904 DiagnosticFactTag::BranchIndex,
905 DiagnosticFactTag::TypeFamily,
906 ],
907 )
908 || tags_match(
909 fact_count,
910 fact_at,
911 &[DiagnosticFactTag::TypeFamily, DiagnosticFactTag::TypeFamily],
912 )
913 || tags_match(
914 fact_count,
915 fact_at,
916 &[
917 DiagnosticFactTag::ClauseIndex,
918 DiagnosticFactTag::AggregateIndex,
919 DiagnosticFactTag::ActualCount,
920 ],
921 )
922 || tags_match(
923 fact_count,
924 fact_at,
925 &[
926 DiagnosticFactTag::FunctionKind,
927 DiagnosticFactTag::ExpectedArity,
928 DiagnosticFactTag::ActualArity,
929 ],
930 )
931 || tags_match(
932 fact_count,
933 fact_at,
934 &[
935 DiagnosticFactTag::FunctionKind,
936 DiagnosticFactTag::ArgumentIndex,
937 DiagnosticFactTag::TypeFamily,
938 ],
939 )
940 || tags_match(
941 fact_count,
942 fact_at,
943 &[
944 DiagnosticFactTag::OperatorKind,
945 DiagnosticFactTag::TypeFamily,
946 DiagnosticFactTag::TypeFamily,
947 ],
948 )
949 || tags_match(
950 fact_count,
951 fact_at,
952 &[
953 DiagnosticFactTag::BranchIndex,
954 DiagnosticFactTag::TypeFamily,
955 DiagnosticFactTag::TypeFamily,
956 ],
957 )
958 || tags_match(
959 fact_count,
960 fact_at,
961 &[
962 DiagnosticFactTag::TypeFamily,
963 DiagnosticFactTag::BranchIndex,
964 DiagnosticFactTag::TypeFamily,
965 ],
966 )
967 || tags_match(
968 fact_count,
969 fact_at,
970 &[
971 DiagnosticFactTag::BranchIndex,
972 DiagnosticFactTag::TypeFamily,
973 DiagnosticFactTag::BranchIndex,
974 DiagnosticFactTag::TypeFamily,
975 ],
976 )
977 || tags_match(
978 fact_count,
979 fact_at,
980 &[
981 DiagnosticFactTag::FunctionKind,
982 DiagnosticFactTag::ArgumentIndex,
983 DiagnosticFactTag::TypeFamily,
984 DiagnosticFactTag::ArgumentIndex,
985 DiagnosticFactTag::TypeFamily,
986 ],
987 )
988}
989
990fn cursor_schema(fact_count: usize, fact_at: &impl Fn(usize) -> (u8, u64)) -> bool {
991 if fact_count == 0 {
992 return true;
993 }
994 if tags_match(fact_count, fact_at, &[DiagnosticFactTag::DecodeReason]) {
995 return matches!(fact_at(0).1, 1 | 3 | 5 | 6 | 7 | 11 | 12);
996 }
997 if tags_match(
998 fact_count,
999 fact_at,
1000 &[
1001 DiagnosticFactTag::ActualLength,
1002 DiagnosticFactTag::Maximum,
1003 DiagnosticFactTag::DecodeReason,
1004 ],
1005 ) {
1006 return fact_at(2).1 == DiagnosticDecodeReason::CursorTooLong.raw();
1007 }
1008 if tags_match(
1009 fact_count,
1010 fact_at,
1011 &[
1012 DiagnosticFactTag::ComponentIndex,
1013 DiagnosticFactTag::DecodeReason,
1014 ],
1015 ) {
1016 return matches!(fact_at(1).1, 4 | 6 | 7);
1017 }
1018 tags_match(
1019 fact_count,
1020 fact_at,
1021 &[
1022 DiagnosticFactTag::ExpectedSignaturePrefix,
1023 DiagnosticFactTag::ActualSignaturePrefix,
1024 ],
1025 ) || tags_match(
1026 fact_count,
1027 fact_at,
1028 &[
1029 DiagnosticFactTag::ExpectedOffset,
1030 DiagnosticFactTag::ActualOffset,
1031 ],
1032 )
1033}
1034
1035fn store_corruption_schema(fact_count: usize, fact_at: &impl Fn(usize) -> (u8, u64)) -> bool {
1036 fact_count == 0
1037 || (tags_match(
1038 fact_count,
1039 fact_at,
1040 &[
1041 DiagnosticFactTag::ComponentKind,
1042 DiagnosticFactTag::ActualLength,
1043 DiagnosticFactTag::Limit,
1044 ],
1045 ) && fact_at(0).1 == DiagnosticComponentKind::CommitDataKey.raw())
1046 || tags_match(
1047 fact_count,
1048 fact_at,
1049 &[
1050 DiagnosticFactTag::ExpectedEntityTag,
1051 DiagnosticFactTag::ActualEntityTag,
1052 ],
1053 )
1054}
1055
1056fn runtime_corruption_schema(fact_count: usize, fact_at: &impl Fn(usize) -> (u8, u64)) -> bool {
1057 store_corruption_schema(fact_count, fact_at)
1058 || (tags_match(fact_count, fact_at, &[DiagnosticFactTag::DecodeReason])
1059 && matches!(fact_at(0).1, 8..=10))
1060}
1061
1062fn incompatible_format_schema(fact_count: usize, fact_at: &impl Fn(usize) -> (u8, u64)) -> bool {
1063 fact_count == 0
1064 || tags_match(fact_count, fact_at, &[DiagnosticFactTag::ExpectedVersion])
1065 || tags_match(
1066 fact_count,
1067 fact_at,
1068 &[
1069 DiagnosticFactTag::ExpectedVersion,
1070 DiagnosticFactTag::ActualVersion,
1071 ],
1072 )
1073}
1074
1075fn runtime_invariant_schema(fact_count: usize, fact_at: &impl Fn(usize) -> (u8, u64)) -> bool {
1076 fact_count == 0
1077 || (tags_match(
1078 fact_count,
1079 fact_at,
1080 &[
1081 DiagnosticFactTag::EntityTag,
1082 DiagnosticFactTag::PhysicalGeneration,
1083 DiagnosticFactTag::ComponentKind,
1084 DiagnosticFactTag::ActualArity,
1085 DiagnosticFactTag::Maximum,
1086 ],
1087 ) && fact_at(2).1 == DiagnosticComponentKind::IndexKey.raw())
1088}
1089
1090fn runtime_conflict_schema(fact_count: usize, fact_at: &impl Fn(usize) -> (u8, u64)) -> bool {
1091 fact_count == 0
1092 || tags_match(fact_count, fact_at, &[DiagnosticFactTag::ExpectedRevision])
1093 || tags_match(
1094 fact_count,
1095 fact_at,
1096 &[
1097 DiagnosticFactTag::ExpectedRevision,
1098 DiagnosticFactTag::CurrentRevision,
1099 ],
1100 )
1101}
1102
1103fn runtime_unsupported_schema(fact_count: usize, fact_at: &impl Fn(usize) -> (u8, u64)) -> bool {
1104 fact_count == 0
1105 || tags_match(
1108 fact_count,
1109 fact_at,
1110 &[DiagnosticFactTag::ActualLength, DiagnosticFactTag::Limit],
1111 )
1112 || (tags_match(
1113 fact_count,
1114 fact_at,
1115 &[
1116 DiagnosticFactTag::EntityTag,
1117 DiagnosticFactTag::PhysicalGeneration,
1118 DiagnosticFactTag::ComponentKind,
1119 DiagnosticFactTag::ActualLength,
1120 DiagnosticFactTag::Limit,
1121 ],
1122 ) && fact_at(2).1 == DiagnosticComponentKind::IndexKey.raw())
1123 || (tags_match(
1124 fact_count,
1125 fact_at,
1126 &[
1127 DiagnosticFactTag::EntityTag,
1128 DiagnosticFactTag::PhysicalGeneration,
1129 DiagnosticFactTag::ComponentIndex,
1130 DiagnosticFactTag::ComponentKind,
1131 DiagnosticFactTag::ActualLength,
1132 DiagnosticFactTag::Limit,
1133 ],
1134 ) && fact_at(3).1 == DiagnosticComponentKind::IndexKeyComponent.raw())
1135}
1136
1137fn runtime_internal_schema(fact_count: usize, fact_at: &impl Fn(usize) -> (u8, u64)) -> bool {
1138 if fact_count >= 2
1141 && fact_at(0).0 == DiagnosticFactTag::EntityTag.raw()
1142 && fact_at(1).0 == DiagnosticFactTag::RelationId.raw()
1143 {
1144 return runtime_internal_detail_schema(fact_count - 2, &|index| fact_at(index + 2));
1145 }
1146 runtime_internal_detail_schema(fact_count, fact_at)
1147}
1148
1149fn runtime_internal_detail_schema(
1150 fact_count: usize,
1151 fact_at: &impl Fn(usize) -> (u8, u64),
1152) -> bool {
1153 fact_count == 0
1154 || tags_match(
1155 fact_count,
1156 fact_at,
1157 &[
1158 DiagnosticFactTag::ExpectedMemoryId,
1159 DiagnosticFactTag::ActualMemoryId,
1160 ],
1161 )
1162 || (tags_match(
1163 fact_count,
1164 fact_at,
1165 &[
1166 DiagnosticFactTag::ComponentKind,
1167 DiagnosticFactTag::ExpectedArity,
1168 DiagnosticFactTag::ActualArity,
1169 ],
1170 ) && fact_at(0).1 == DiagnosticComponentKind::RelationTargetPrimaryKey.raw())
1171}
1172
1173fn constraint_schema(
1174 fact_count: usize,
1175 fact_at: &impl Fn(usize) -> (u8, u64),
1176 allow_targeted_path: bool,
1177) -> bool {
1178 const COMMON: &[DiagnosticFactTag] = &[
1179 DiagnosticFactTag::AcceptedSchemaFingerprintMethod,
1180 DiagnosticFactTag::AcceptedSchemaFingerprintHigh,
1181 DiagnosticFactTag::AcceptedSchemaFingerprintLow,
1182 DiagnosticFactTag::EntityTag,
1183 DiagnosticFactTag::ConstraintId,
1184 DiagnosticFactTag::ConstraintKind,
1185 DiagnosticFactTag::ConstraintContext,
1186 ];
1187 if fact_count < COMMON.len()
1188 || !tags_prefix_matches(fact_count, fact_at, COMMON)
1189 || fact_at(6).1 != DiagnosticConstraintContext::WriteAdmission.raw()
1190 {
1191 return false;
1192 }
1193
1194 let constraint_kind = fact_at(5).1;
1195 if DiagnosticConstraintKind::known(constraint_kind).is_none() {
1196 return false;
1197 }
1198 let mut index = COMMON.len();
1199 if index < fact_count && fact_at(index).0 == DiagnosticFactTag::MutationOperation.raw() {
1200 index += 1;
1201 if index < fact_count && fact_at(index).0 == DiagnosticFactTag::BatchPosition.raw() {
1202 index += 1;
1203 }
1204 }
1205
1206 let path_len = fact_count - index;
1207 if path_len == 0 {
1208 return !allow_targeted_path
1209 || constraint_kind != DiagnosticConstraintKind::TargetedRule.raw();
1210 }
1211 allow_targeted_path
1212 && constraint_kind == DiagnosticConstraintKind::TargetedRule.raw()
1213 && path_len <= 64
1214 && (index..fact_count).all(|position| {
1215 DiagnosticFactTag::known(fact_at(position).0).is_some_and(is_value_path_tag)
1216 })
1217}
1218
1219fn mutation_schema(
1222 fact_count: usize,
1223 fact_at: &impl Fn(usize) -> (u8, u64),
1224 required: &[DiagnosticFactTag],
1225) -> bool {
1226 tags_prefix_matches(fact_count, fact_at, required)
1227 && (fact_count == required.len()
1228 || (fact_count == required.len() + 1
1229 && fact_at(required.len()).0 == DiagnosticFactTag::BatchPosition.raw()))
1230}
1231
1232fn tags_match(
1233 fact_count: usize,
1234 fact_at: &impl Fn(usize) -> (u8, u64),
1235 expected: &[DiagnosticFactTag],
1236) -> bool {
1237 fact_count == expected.len() && tags_prefix_matches(fact_count, fact_at, expected)
1238}
1239
1240fn tags_prefix_matches(
1241 fact_count: usize,
1242 fact_at: &impl Fn(usize) -> (u8, u64),
1243 expected: &[DiagnosticFactTag],
1244) -> bool {
1245 fact_count >= expected.len()
1246 && expected
1247 .iter()
1248 .enumerate()
1249 .all(|(index, tag)| fact_at(index).0 == tag.raw())
1250}
1251
1252const fn is_value_path_tag(tag: DiagnosticFactTag) -> bool {
1253 matches!(
1254 tag,
1255 DiagnosticFactTag::RootField
1256 | DiagnosticFactTag::RecordMember
1257 | DiagnosticFactTag::TupleElement
1258 | DiagnosticFactTag::Newtype
1259 | DiagnosticFactTag::EnumVariant
1260 | DiagnosticFactTag::ListElement
1261 | DiagnosticFactTag::SetElement
1262 | DiagnosticFactTag::MapEntryKey
1263 | DiagnosticFactTag::MapEntryValue
1264 )
1265}
1266
1267const fn diagnostic_fact_value_is_valid(tag: DiagnosticFactTag, value: u64) -> bool {
1268 match tag {
1269 DiagnosticFactTag::AcceptedSchemaFingerprintMethod
1270 | DiagnosticFactTag::ExpectedMemoryId
1271 | DiagnosticFactTag::ActualMemoryId => value <= u8::MAX as u64,
1272 DiagnosticFactTag::ConstraintId
1273 | DiagnosticFactTag::FieldId
1274 | DiagnosticFactTag::IndexId
1275 | DiagnosticFactTag::RelationId
1276 | DiagnosticFactTag::BatchPosition
1277 | DiagnosticFactTag::FirstBatchPosition
1278 | DiagnosticFactTag::DuplicateBatchPosition
1279 | DiagnosticFactTag::RowLayout
1280 | DiagnosticFactTag::HistoryFloor
1281 | DiagnosticFactTag::CurrentLayout
1282 | DiagnosticFactTag::RootField
1283 | DiagnosticFactTag::Newtype
1284 | DiagnosticFactTag::ListElement
1285 | DiagnosticFactTag::SetElement
1286 | DiagnosticFactTag::MapEntryKey
1287 | DiagnosticFactTag::MapEntryValue => value <= u32::MAX as u64,
1288 DiagnosticFactTag::ConstraintKind => DiagnosticConstraintKind::known(value).is_some(),
1289 DiagnosticFactTag::BacklogResource => DiagnosticBacklogResource::known(value).is_some(),
1290 DiagnosticFactTag::GroupedLimit => DiagnosticGroupBudgetResource::known(value).is_some(),
1291 DiagnosticFactTag::ConstraintContext => DiagnosticConstraintContext::known(value).is_some(),
1292 DiagnosticFactTag::TypeFamily => DiagnosticTypeFamily::known(value).is_some(),
1293 DiagnosticFactTag::FunctionKind => DiagnosticFunctionKind::known(value).is_some(),
1294 DiagnosticFactTag::OperatorKind => DiagnosticOperatorKind::known(value).is_some(),
1295 DiagnosticFactTag::AggregateKind => DiagnosticAggregateKind::known(value).is_some(),
1296 DiagnosticFactTag::ComponentKind => DiagnosticComponentKind::known(value).is_some(),
1297 DiagnosticFactTag::DecodeReason => DiagnosticDecodeReason::known(value).is_some(),
1298 DiagnosticFactTag::BudgetResource => {
1299 DiagnosticExecutionBudgetResource::known(value).is_some()
1300 }
1301 DiagnosticFactTag::ExecutionBudgetScope => {
1302 DiagnosticExecutionBudgetScope::known(value).is_some()
1303 }
1304 DiagnosticFactTag::ExecutionLane => DiagnosticExecutionLane::known(value).is_some(),
1305 DiagnosticFactTag::MutationOperation => DiagnosticMutationOperation::known(value).is_some(),
1306 _ => true,
1307 }
1308}
1309
1310#[cfg(test)]
1311mod tests {
1312 use super::{
1313 DiagnosticAggregateKind, DiagnosticBacklogResource, DiagnosticComponentKind,
1314 DiagnosticConstraintContext, DiagnosticConstraintKind, DiagnosticDecodeReason,
1315 DiagnosticExecutionBudgetResource, DiagnosticExecutionBudgetScope, DiagnosticExecutionLane,
1316 DiagnosticFactSchemaMismatch, DiagnosticFactTag, DiagnosticFunctionKind,
1317 DiagnosticGroupBudgetResource, DiagnosticMutationOperation, DiagnosticOperatorKind,
1318 DiagnosticTypeFamily, ORDERED_FACT_TAGS, pack_u32_pair, unpack_u32_pair,
1319 validate_known_diagnostic_fact_schema, validate_raw_diagnostic_fact_schema,
1320 };
1321 use crate::ErrorCode;
1322
1323 #[test]
1324 fn schema_page_budget_facts_distinguish_counts_and_bytes() {
1325 let code = ErrorCode::SCHEMA_DDL_TRANSITION_BUDGET_EXCEEDED;
1326 assert_eq!(validate_known_diagnostic_fact_schema(code, &[]), Ok(()));
1327 for (tag, limit) in [
1328 (DiagnosticFactTag::ActualCount, 256),
1329 (DiagnosticFactTag::ActualLength, 4 * 1024 * 1024),
1330 ] {
1331 let facts = [(DiagnosticFactTag::Limit, limit), (tag, limit + 1)];
1332 assert_eq!(validate_known_diagnostic_fact_schema(code, &facts), Ok(()));
1333 assert!(validate_known_diagnostic_fact_schema(code, &facts[..1]).is_err());
1334 assert!(validate_known_diagnostic_fact_schema(code, &[facts[1], facts[0]]).is_err());
1335 }
1336 }
1337
1338 #[test]
1339 fn serialized_size_limit_facts_require_both_ordered_byte_counts() {
1340 let code = ErrorCode::RUNTIME_UNSUPPORTED;
1341 let actual = (DiagnosticFactTag::ActualLength, 4_194_305);
1342 let limit = (DiagnosticFactTag::Limit, 4_194_304);
1343 assert_eq!(
1344 validate_known_diagnostic_fact_schema(code, &[actual, limit]),
1345 Ok(())
1346 );
1347 for facts in [
1348 vec![actual],
1349 vec![limit],
1350 vec![limit, actual],
1351 vec![actual, limit, limit],
1352 ] {
1353 assert!(validate_known_diagnostic_fact_schema(code, &facts).is_err());
1354 }
1355 }
1356
1357 #[test]
1358 fn memory_admission_fact_schemas_keep_counts_and_ids_bounded() {
1359 for code in [
1360 ErrorCode::RUNTIME_BOUNDARY_MEMORY_ALLOCATION_RESOLUTION_FAILED,
1361 ErrorCode::RUNTIME_BOUNDARY_MEMORY_HISTORICAL_JOURNAL_UNAVAILABLE,
1362 ] {
1363 assert_eq!(validate_known_diagnostic_fact_schema(code, &[]), Ok(()));
1364 for id in [0, 42, 254] {
1365 assert_eq!(
1366 validate_known_diagnostic_fact_schema(
1367 code,
1368 &[(DiagnosticFactTag::ActualMemoryId, id)]
1369 ),
1370 Ok(())
1371 );
1372 }
1373 for facts in [
1374 vec![(DiagnosticFactTag::ActualMemoryId, 255)],
1375 vec![(DiagnosticFactTag::ExpectedMemoryId, 42)],
1376 ] {
1377 assert!(validate_known_diagnostic_fact_schema(code, &facts).is_err());
1378 }
1379 }
1380 let roles = ErrorCode::RUNTIME_BOUNDARY_MEMORY_ALLOCATION_ROLES_INCOMPLETE;
1381 for count in [3, 4] {
1382 assert_eq!(
1383 validate_known_diagnostic_fact_schema(
1384 roles,
1385 &[(DiagnosticFactTag::ExpectedCount, count)]
1386 ),
1387 Ok(())
1388 );
1389 }
1390 for facts in [
1391 vec![],
1392 vec![(DiagnosticFactTag::ExpectedCount, 0)],
1393 vec![(DiagnosticFactTag::ExpectedCount, 5)],
1394 vec![(DiagnosticFactTag::ActualCount, 3)],
1395 ] {
1396 assert!(validate_known_diagnostic_fact_schema(roles, &facts).is_err());
1397 }
1398 let mismatch = ErrorCode::RUNTIME_BOUNDARY_MEMORY_DECLARATION_SNAPSHOT_MISMATCH;
1399 assert_eq!(validate_known_diagnostic_fact_schema(mismatch, &[]), Ok(()));
1400 assert_eq!(
1401 validate_known_diagnostic_fact_schema(
1402 mismatch,
1403 &[
1404 (DiagnosticFactTag::ExpectedMemoryId, 0),
1405 (DiagnosticFactTag::ActualMemoryId, 254)
1406 ]
1407 ),
1408 Ok(())
1409 );
1410 for facts in [
1411 vec![(DiagnosticFactTag::ExpectedMemoryId, 42)],
1412 vec![
1413 (DiagnosticFactTag::ExpectedMemoryId, 42),
1414 (DiagnosticFactTag::ActualMemoryId, 42),
1415 ],
1416 vec![
1417 (DiagnosticFactTag::ActualMemoryId, 42),
1418 (DiagnosticFactTag::ExpectedMemoryId, 43),
1419 ],
1420 vec![
1421 (DiagnosticFactTag::ExpectedMemoryId, 255),
1422 (DiagnosticFactTag::ActualMemoryId, 42),
1423 ],
1424 ] {
1425 assert!(validate_known_diagnostic_fact_schema(mismatch, &facts).is_err());
1426 }
1427 for code in [
1428 ErrorCode::RUNTIME_BOUNDARY_MEMORY_NAMESPACE_REMOVED,
1429 ErrorCode::RUNTIME_BOUNDARY_MEMORY_DECLARATION_INVALID,
1430 ] {
1431 assert_eq!(validate_known_diagnostic_fact_schema(code, &[]), Ok(()));
1432 assert!(
1433 validate_known_diagnostic_fact_schema(
1434 code,
1435 &[(DiagnosticFactTag::ActualMemoryId, 42)]
1436 )
1437 .is_err()
1438 );
1439 }
1440 }
1441
1442 #[test]
1443 fn bucket_mismatch_requires_two_distinct_nonzero_page_counts() {
1444 let code = ErrorCode::RUNTIME_BOUNDARY_MEMORY_BUCKET_SIZE_MISMATCH;
1445 let expected = DiagnosticFactTag::Expected;
1446 let actual = DiagnosticFactTag::Actual;
1447 assert_eq!(
1448 validate_known_diagnostic_fact_schema(code, &[(expected, 16), (actual, 128)]),
1449 Ok(()),
1450 );
1451 for facts in [
1452 vec![],
1453 vec![(expected, 16)],
1454 vec![(actual, 128), (expected, 16)],
1455 vec![(expected, 16), (expected, 128)],
1456 vec![(expected, 16), (actual, 16)],
1457 vec![(expected, 0), (actual, 128)],
1458 vec![(expected, 16), (actual, 0)],
1459 vec![(expected, 65_536), (actual, 128)],
1460 vec![(expected, 16), (actual, 65_536)],
1461 ] {
1462 assert_eq!(
1463 validate_known_diagnostic_fact_schema(code, &facts),
1464 Err(DiagnosticFactSchemaMismatch::InvalidSequence),
1465 );
1466 }
1467 assert_eq!(
1468 validate_known_diagnostic_fact_schema(
1469 code,
1470 &[(expected, 16), (actual, 128), (actual, 128)],
1471 ),
1472 Err(DiagnosticFactSchemaMismatch::CodeMaximumExceeded),
1473 );
1474 }
1475
1476 #[test]
1477 fn fact_tag_registry_is_fixed_unique_and_contiguous() {
1478 for (index, tag) in ORDERED_FACT_TAGS.iter().copied().enumerate() {
1479 let expected = u8::try_from(index + 1).expect("fact-tag index fits u8");
1480 assert_eq!(tag.raw(), expected);
1481 assert_eq!(DiagnosticFactTag::known(expected), Some(tag));
1482 }
1483
1484 assert_eq!(DiagnosticFactTag::known(0), None);
1485 assert_eq!(DiagnosticFactTag::known(97), None);
1486 assert_eq!(DiagnosticFactTag::known(u8::MAX), None);
1487 }
1488
1489 #[test]
1490 fn execution_budget_fact_value_registries_are_fixed() {
1491 assert_eq!(DiagnosticBacklogResource::Batches.raw(), 1);
1492 assert_eq!(DiagnosticBacklogResource::Records.raw(), 2);
1493 assert_eq!(DiagnosticBacklogResource::EncodedBytes.raw(), 3);
1494 assert_eq!(DiagnosticBacklogResource::known(4), None);
1495
1496 for (index, resource) in DiagnosticExecutionBudgetResource::ALL
1497 .iter()
1498 .copied()
1499 .enumerate()
1500 {
1501 let expected = u64::try_from(index + 1).expect("resource index fits u64");
1502 assert_eq!(resource.raw(), expected);
1503 assert_eq!(
1504 DiagnosticExecutionBudgetResource::known(expected),
1505 Some(resource)
1506 );
1507 }
1508 assert_eq!(DiagnosticExecutionBudgetResource::known(0), None);
1509 assert_eq!(DiagnosticExecutionBudgetResource::known(21), None);
1510
1511 assert_eq!(DiagnosticExecutionBudgetScope::Execution.raw(), 1);
1512 assert_eq!(DiagnosticExecutionBudgetScope::Request.raw(), 2);
1513 assert_eq!(DiagnosticExecutionBudgetScope::known(3), None);
1514
1515 assert_eq!(DiagnosticExecutionLane::PublicRead.raw(), 1);
1516 assert_eq!(DiagnosticExecutionLane::TrustedRead.raw(), 2);
1517 assert_eq!(DiagnosticExecutionLane::Diagnostic.raw(), 3);
1518 assert_eq!(DiagnosticExecutionLane::Mutation.raw(), 4);
1519 assert_eq!(DiagnosticExecutionLane::Recovery.raw(), 5);
1520 assert_eq!(DiagnosticExecutionLane::known(6), None);
1521 }
1522
1523 #[test]
1524 fn accepted_identity_pair_packing_is_exact() {
1525 for pair in [
1526 (0, 0),
1527 (1, 2),
1528 (u32::MAX, 0),
1529 (0, u32::MAX),
1530 (u32::MAX, u32::MAX),
1531 ] {
1532 assert_eq!(unpack_u32_pair(pack_u32_pair(pair.0, pair.1)), pair);
1533 }
1534 }
1535
1536 #[test]
1537 fn constraint_fact_value_registries_are_fixed() {
1538 assert_eq!(DiagnosticConstraintKind::Check.raw(), 1);
1539 assert_eq!(DiagnosticConstraintKind::NotNull.raw(), 2);
1540 assert_eq!(DiagnosticConstraintKind::Relation.raw(), 3);
1541 assert_eq!(DiagnosticConstraintKind::TargetedRule.raw(), 4);
1542 assert_eq!(DiagnosticConstraintKind::Unique.raw(), 5);
1543 assert_eq!(DiagnosticConstraintKind::known(0), None);
1544 assert_eq!(DiagnosticConstraintKind::known(6), None);
1545
1546 assert_eq!(DiagnosticConstraintContext::Integrity.raw(), 1);
1547 assert_eq!(DiagnosticConstraintContext::MigrationValidation.raw(), 2);
1548 assert_eq!(DiagnosticConstraintContext::WriteAdmission.raw(), 3);
1549 assert_eq!(DiagnosticConstraintContext::known(0), None);
1550 assert_eq!(DiagnosticConstraintContext::known(4), None);
1551 }
1552
1553 #[test]
1554 fn component_kind_registry_is_fixed_and_numeric() {
1555 let kinds = [
1556 DiagnosticComponentKind::CommitDataKey,
1557 DiagnosticComponentKind::IndexKey,
1558 DiagnosticComponentKind::IndexKeyComponent,
1559 DiagnosticComponentKind::RelationTargetPrimaryKey,
1560 ];
1561
1562 for (index, kind) in kinds.iter().copied().enumerate() {
1563 let expected = (index + 1) as u64;
1564 assert_eq!(kind.raw(), expected);
1565 assert_eq!(DiagnosticComponentKind::known(expected), Some(kind));
1566 assert_eq!(format!("{kind:?}"), expected.to_string());
1567 }
1568 assert_eq!(DiagnosticComponentKind::known(0), None);
1569 assert_eq!(DiagnosticComponentKind::known(5), None);
1570 }
1571
1572 #[test]
1573 fn decode_reason_registry_is_fixed_and_numeric() {
1574 let reasons = [
1575 DiagnosticDecodeReason::CursorEmpty,
1576 DiagnosticDecodeReason::CursorTooLong,
1577 DiagnosticDecodeReason::CursorInvalidLength,
1578 DiagnosticDecodeReason::CursorInvalidBase64,
1579 DiagnosticDecodeReason::CursorGroupedDirectionMismatch,
1580 DiagnosticDecodeReason::CursorTokenEncode,
1581 DiagnosticDecodeReason::CursorTokenDecode,
1582 DiagnosticDecodeReason::RecoveryMarkerMagic,
1583 DiagnosticDecodeReason::RecoveryMarkerChecksum,
1584 DiagnosticDecodeReason::RecoveryMarkerState,
1585 DiagnosticDecodeReason::CursorGroupedContinuationRequiresLimit,
1586 DiagnosticDecodeReason::CursorGlobalDistinctContinuationUnsupported,
1587 ];
1588
1589 for (index, reason) in reasons.iter().copied().enumerate() {
1590 let expected = (index + 1) as u64;
1591 assert_eq!(reason.raw(), expected);
1592 assert_eq!(DiagnosticDecodeReason::known(expected), Some(reason));
1593 assert_eq!(format!("{reason:?}"), expected.to_string());
1594 }
1595
1596 assert_eq!(DiagnosticDecodeReason::known(0), None);
1597 assert_eq!(DiagnosticDecodeReason::known(13), None);
1598 }
1599
1600 #[test]
1601 fn mutation_operation_registry_is_fixed_and_numeric() {
1602 let operations = [
1603 DiagnosticMutationOperation::Insert,
1604 DiagnosticMutationOperation::Replace,
1605 DiagnosticMutationOperation::Update,
1606 DiagnosticMutationOperation::Delete,
1607 ];
1608
1609 for (index, operation) in operations.iter().copied().enumerate() {
1610 let expected = (index + 1) as u64;
1611 assert_eq!(operation.raw(), expected);
1612 assert_eq!(
1613 DiagnosticMutationOperation::known(expected),
1614 Some(operation)
1615 );
1616 assert_eq!(format!("{operation:?}"), expected.to_string());
1617 }
1618
1619 assert_eq!(DiagnosticMutationOperation::known(0), None);
1620 assert_eq!(DiagnosticMutationOperation::known(5), None);
1621 }
1622
1623 #[test]
1624 fn query_kind_registries_are_fixed_contiguous_and_numeric() {
1625 for raw in 1..=9 {
1626 let value = DiagnosticTypeFamily::known(raw).expect("type family should be known");
1627 assert_eq!(value.raw(), raw);
1628 assert_eq!(format!("{value:?}"), raw.to_string());
1629 }
1630 assert_eq!(DiagnosticTypeFamily::known(0), None);
1631 assert_eq!(DiagnosticTypeFamily::known(10), None);
1632
1633 for raw in 1..=39 {
1634 let value = DiagnosticFunctionKind::known(raw).expect("function kind should be known");
1635 assert_eq!(value.raw(), raw);
1636 assert_eq!(format!("{value:?}"), raw.to_string());
1637 }
1638 assert_eq!(DiagnosticFunctionKind::known(0), None);
1639 assert_eq!(DiagnosticFunctionKind::known(40), None);
1640
1641 for raw in 1..=18 {
1642 let value = DiagnosticOperatorKind::known(raw).expect("operator kind should be known");
1643 assert_eq!(value.raw(), raw);
1644 assert_eq!(format!("{value:?}"), raw.to_string());
1645 }
1646 assert_eq!(DiagnosticOperatorKind::known(0), None);
1647 assert_eq!(DiagnosticOperatorKind::known(19), None);
1648
1649 for raw in 1..=8 {
1650 let value =
1651 DiagnosticAggregateKind::known(raw).expect("aggregate kind should be known");
1652 assert_eq!(value.raw(), raw);
1653 assert_eq!(format!("{value:?}"), raw.to_string());
1654 }
1655 assert_eq!(DiagnosticAggregateKind::known(0), None);
1656 assert_eq!(DiagnosticAggregateKind::known(9), None);
1657 }
1658
1659 #[test]
1660 fn per_code_schema_rejects_missing_disallowed_and_noncanonical_tags() {
1661 let valid = [
1662 (DiagnosticFactTag::ActualCount, 5),
1663 (DiagnosticFactTag::Limit, 4),
1664 ];
1665 assert_eq!(
1666 validate_known_diagnostic_fact_schema(
1667 ErrorCode::RUNTIME_BOUNDARY_MUTATION_BATCH_TOO_MANY_ITEMS,
1668 &valid,
1669 ),
1670 Ok(())
1671 );
1672 assert_eq!(
1673 validate_known_diagnostic_fact_schema(
1674 ErrorCode::RUNTIME_BOUNDARY_MUTATION_BATCH_TOO_MANY_ITEMS,
1675 &valid[..1],
1676 ),
1677 Err(DiagnosticFactSchemaMismatch::InvalidSequence)
1678 );
1679 assert_eq!(
1680 validate_known_diagnostic_fact_schema(
1681 ErrorCode::RUNTIME_BOUNDARY_MUTATION_BATCH_TOO_MANY_ITEMS,
1682 &[valid[1], valid[0]],
1683 ),
1684 Err(DiagnosticFactSchemaMismatch::InvalidSequence)
1685 );
1686 assert_eq!(
1687 validate_known_diagnostic_fact_schema(ErrorCode::QUERY_VALIDATE, &valid),
1688 Err(DiagnosticFactSchemaMismatch::CodeMaximumExceeded)
1689 );
1690
1691 assert_eq!(
1692 validate_known_diagnostic_fact_schema(
1693 ErrorCode::RUNTIME_BOUNDARY_MUTATION_BATCH_COMMIT_WORK_EXCEEDED,
1694 &valid,
1695 ),
1696 Ok(())
1697 );
1698 assert_eq!(
1699 validate_known_diagnostic_fact_schema(
1700 ErrorCode::RUNTIME_BOUNDARY_MUTATION_BATCH_COMMIT_WORK_EXCEEDED,
1701 &valid[1..],
1702 ),
1703 Ok(())
1704 );
1705 }
1706
1707 #[test]
1708 fn grouped_limit_facts_require_matching_resources_and_valid_values() {
1709 let code = ErrorCode::RUNTIME_BOUNDARY_EXECUTION_BUDGET_EXCEEDED;
1710 for resource in [
1711 DiagnosticGroupBudgetResource::Groups,
1712 DiagnosticGroupBudgetResource::DistinctValuesPerGroup,
1713 DiagnosticGroupBudgetResource::DistinctValuesTotal,
1714 DiagnosticGroupBudgetResource::EstimatedBytes,
1715 ] {
1716 assert_eq!(
1717 DiagnosticGroupBudgetResource::known(resource.raw()),
1718 Some(resource)
1719 );
1720 let mut facts = vec![
1721 (
1722 DiagnosticFactTag::BudgetResource,
1723 resource.execution_resource().raw(),
1724 ),
1725 (DiagnosticFactTag::Limit, 10),
1726 (DiagnosticFactTag::Actual, 11),
1727 (DiagnosticFactTag::ExecutionBudgetScope, 1),
1728 (DiagnosticFactTag::ExecutionLane, 2),
1729 (DiagnosticFactTag::QueryShapeFingerprintPrefix, 17),
1730 (DiagnosticFactTag::GroupedLimit, resource.raw()),
1731 ];
1732 assert_eq!(validate_known_diagnostic_fact_schema(code, &facts), Ok(()));
1733 assert_eq!(
1734 validate_known_diagnostic_fact_schema(code, &facts[..6]),
1735 Ok(())
1736 );
1737 facts[0].1 = DiagnosticExecutionBudgetResource::StoredBytesRead.raw();
1738 assert_eq!(
1739 validate_known_diagnostic_fact_schema(code, &facts),
1740 Err(DiagnosticFactSchemaMismatch::InvalidSequence)
1741 );
1742 facts[0].1 = resource.execution_resource().raw();
1743 facts[6].1 = 5;
1744 assert_eq!(
1745 validate_known_diagnostic_fact_schema(code, &facts),
1746 Err(DiagnosticFactSchemaMismatch::InvalidSequence)
1747 );
1748 facts[6].1 = resource.raw();
1749 facts.swap(5, 6);
1750 assert_eq!(
1751 validate_known_diagnostic_fact_schema(code, &facts),
1752 Err(DiagnosticFactSchemaMismatch::InvalidSequence)
1753 );
1754 }
1755 assert_eq!(DiagnosticGroupBudgetResource::known(0), None);
1756 }
1757
1758 #[test]
1759 fn execution_budget_schema_requires_complete_typed_attribution() {
1760 let valid = [
1761 (
1762 DiagnosticFactTag::BudgetResource,
1763 DiagnosticExecutionBudgetResource::StoredBytesRead.raw(),
1764 ),
1765 (DiagnosticFactTag::Limit, 4_096),
1766 (DiagnosticFactTag::Actual, 4_097),
1767 (
1768 DiagnosticFactTag::ExecutionBudgetScope,
1769 DiagnosticExecutionBudgetScope::Request.raw(),
1770 ),
1771 (
1772 DiagnosticFactTag::ExecutionLane,
1773 DiagnosticExecutionLane::TrustedRead.raw(),
1774 ),
1775 (DiagnosticFactTag::QueryShapeFingerprintPrefix, 17),
1776 ];
1777 assert_eq!(
1778 validate_known_diagnostic_fact_schema(
1779 ErrorCode::RUNTIME_BOUNDARY_EXECUTION_BUDGET_EXCEEDED,
1780 &valid,
1781 ),
1782 Ok(())
1783 );
1784 assert_eq!(
1785 validate_known_diagnostic_fact_schema(
1786 ErrorCode::RUNTIME_BOUNDARY_EXECUTION_BUDGET_EXCEEDED,
1787 &valid[..5],
1788 ),
1789 Err(DiagnosticFactSchemaMismatch::InvalidSequence)
1790 );
1791
1792 let mut invalid_resource = valid;
1793 invalid_resource[0].1 = 0;
1794 assert_eq!(
1795 validate_known_diagnostic_fact_schema(
1796 ErrorCode::RUNTIME_BOUNDARY_EXECUTION_BUDGET_EXCEEDED,
1797 &invalid_resource,
1798 ),
1799 Err(DiagnosticFactSchemaMismatch::InvalidValue)
1800 );
1801 }
1802
1803 #[test]
1804 fn raw_schema_keeps_unknown_context_numeric_but_marks_it_invalid() {
1805 assert_eq!(
1806 validate_raw_diagnostic_fact_schema(
1807 ErrorCode::QUERY_INVALID_CONTINUATION_CURSOR,
1808 &[(u8::MAX, 17)],
1809 ),
1810 Err(DiagnosticFactSchemaMismatch::InvalidSequence)
1811 );
1812 assert_eq!(
1813 validate_raw_diagnostic_fact_schema(
1814 ErrorCode::QUERY_INVALID_CONTINUATION_CURSOR,
1815 &[(DiagnosticFactTag::DecodeReason.raw(), u64::MAX)],
1816 ),
1817 Err(DiagnosticFactSchemaMismatch::InvalidSequence)
1818 );
1819 }
1820
1821 #[test]
1822 fn constraint_schema_enforces_authority_operation_and_bounded_path_suffix() {
1823 let mut targeted = vec![
1824 (DiagnosticFactTag::AcceptedSchemaFingerprintMethod, 1),
1825 (DiagnosticFactTag::AcceptedSchemaFingerprintHigh, 2),
1826 (DiagnosticFactTag::AcceptedSchemaFingerprintLow, 3),
1827 (DiagnosticFactTag::EntityTag, 17),
1828 (DiagnosticFactTag::ConstraintId, 4),
1829 (
1830 DiagnosticFactTag::ConstraintKind,
1831 DiagnosticConstraintKind::TargetedRule.raw(),
1832 ),
1833 (
1834 DiagnosticFactTag::ConstraintContext,
1835 DiagnosticConstraintContext::WriteAdmission.raw(),
1836 ),
1837 (
1838 DiagnosticFactTag::MutationOperation,
1839 DiagnosticMutationOperation::Insert.raw(),
1840 ),
1841 (DiagnosticFactTag::BatchPosition, 0),
1842 ];
1843 targeted.extend((0..64).map(|index| (DiagnosticFactTag::ListElement, index)));
1844 assert_eq!(targeted.len(), 73);
1845 assert_eq!(
1846 validate_known_diagnostic_fact_schema(
1847 ErrorCode::RUNTIME_BOUNDARY_CONSTRAINT_VIOLATION,
1848 targeted.as_slice(),
1849 ),
1850 Ok(())
1851 );
1852
1853 let mut overlong = targeted.clone();
1854 overlong.push((DiagnosticFactTag::ListElement, 64));
1855 assert_eq!(
1856 validate_known_diagnostic_fact_schema(
1857 ErrorCode::RUNTIME_BOUNDARY_CONSTRAINT_VIOLATION,
1858 overlong.as_slice(),
1859 ),
1860 Err(DiagnosticFactSchemaMismatch::CodeMaximumExceeded)
1861 );
1862
1863 let mut non_targeted_path = targeted;
1864 non_targeted_path[5].1 = DiagnosticConstraintKind::Unique.raw();
1865 assert_eq!(
1866 validate_known_diagnostic_fact_schema(
1867 ErrorCode::RUNTIME_BOUNDARY_CONSTRAINT_VIOLATION,
1868 non_targeted_path.as_slice(),
1869 ),
1870 Err(DiagnosticFactSchemaMismatch::InvalidSequence)
1871 );
1872 }
1873
1874 #[test]
1875 fn schema_enforces_global_ceiling_before_per_code_ceiling() {
1876 let facts = vec![(DiagnosticFactTag::ActualCount.raw(), 0); 81];
1877 assert_eq!(
1878 validate_raw_diagnostic_fact_schema(ErrorCode::QUERY_PLAN, facts.as_slice()),
1879 Err(DiagnosticFactSchemaMismatch::GlobalMaximumExceeded)
1880 );
1881 }
1882}