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}
147
148#[derive(Clone, Copy, Eq, Hash, PartialEq)]
153pub enum DiagnosticDecodeReason {
154 CursorEmpty,
155 CursorTooLong,
156 CursorOddLength,
157 CursorInvalidHex,
158 CursorGroupedDirectionMismatch,
159 CursorTokenEncode,
160 CursorTokenDecode,
161 RecoveryMarkerMagic,
162 RecoveryMarkerChecksum,
163 RecoveryMarkerState,
164}
165
166#[derive(Clone, Copy, Eq, Hash, PartialEq)]
168pub enum DiagnosticMutationOperation {
169 Insert,
170 Replace,
171 Update,
172 Delete,
173}
174
175macro_rules! define_numeric_fact_value_registry {
176 (
177 $(#[$enum_meta:meta])*
178 pub enum $name:ident {
179 $($variant:ident = $raw:literal;)+
180 }
181 ) => {
182 $(#[$enum_meta])*
183 #[derive(Clone, Copy, Eq, Hash, PartialEq)]
184 pub enum $name {
185 $(
186 #[doc = concat!("Compact diagnostic value ", stringify!($raw), ".")]
187 $variant,
188 )+
189 }
190
191 impl $name {
192 #[must_use]
194 pub const fn raw(self) -> u64 {
195 match self {
196 $(Self::$variant => $raw,)+
197 }
198 }
199
200 #[must_use]
202 pub const fn known(raw: u64) -> Option<Self> {
203 match raw {
204 $($raw => Some(Self::$variant),)+
205 _ => None,
206 }
207 }
208 }
209
210 impl fmt::Debug for $name {
211 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
212 write!(f, "{}", self.raw())
213 }
214 }
215 };
216}
217
218define_numeric_fact_value_registry! {
219 pub enum DiagnosticExecutionBudgetResource {
221 QueryExecutions = 1;
222 PlanningSteps = 2;
223 PlanCompilations = 3;
224 KeyIndexEntriesVisited = 4;
225 RowsVisited = 5;
226 StoredBytesRead = 6;
227 PredicateExpressionSteps = 7;
228 NestedValueSteps = 8;
229 DecodedBytes = 9;
230 MaterializedBytes = 10;
231 SortEntries = 11;
232 SortComparisons = 12;
233 SortTemporaryBytes = 13;
234 GroupDistinctEntries = 14;
235 GroupDistinctStateBytes = 15;
236 CursorSteps = 16;
237 TemporaryBytes = 17;
238 DiagnosticSteps = 18;
239 ResultRows = 19;
240 ResultBytes = 20;
241 InstructionUnits = 21;
242 }
243}
244
245impl DiagnosticExecutionBudgetResource {
246 pub const ALL: [Self; 21] = [
248 Self::QueryExecutions,
249 Self::PlanningSteps,
250 Self::PlanCompilations,
251 Self::KeyIndexEntriesVisited,
252 Self::RowsVisited,
253 Self::StoredBytesRead,
254 Self::PredicateExpressionSteps,
255 Self::NestedValueSteps,
256 Self::DecodedBytes,
257 Self::MaterializedBytes,
258 Self::SortEntries,
259 Self::SortComparisons,
260 Self::SortTemporaryBytes,
261 Self::GroupDistinctEntries,
262 Self::GroupDistinctStateBytes,
263 Self::CursorSteps,
264 Self::TemporaryBytes,
265 Self::DiagnosticSteps,
266 Self::ResultRows,
267 Self::ResultBytes,
268 Self::InstructionUnits,
269 ];
270}
271
272define_numeric_fact_value_registry! {
273 pub enum DiagnosticExecutionBudgetScope {
275 Execution = 1;
276 Request = 2;
277 }
278}
279
280define_numeric_fact_value_registry! {
281 pub enum DiagnosticExecutionLane {
283 PublicRead = 1;
284 TrustedRead = 2;
285 Diagnostic = 3;
286 Mutation = 4;
287 Recovery = 5;
288 }
289}
290
291define_numeric_fact_value_registry! {
292 pub enum DiagnosticConstraintKind {
294 Check = 1;
295 NotNull = 2;
296 Relation = 3;
297 TargetedRule = 4;
298 Unique = 5;
299 }
300}
301
302define_numeric_fact_value_registry! {
303 pub enum DiagnosticConstraintContext {
305 Integrity = 1;
306 MigrationValidation = 2;
307 WriteAdmission = 3;
308 }
309}
310
311define_numeric_fact_value_registry! {
312 pub enum DiagnosticComponentKind {
314 CommitDataKey = 1;
315 IndexKey = 2;
316 IndexKeyComponent = 3;
317 RelationTargetPrimaryKey = 4;
318 }
319}
320
321define_numeric_fact_value_registry! {
322 pub enum DiagnosticTypeFamily {
324 Blob = 1;
325 Bool = 2;
326 Collection = 3;
327 Null = 4;
328 Numeric = 5;
329 Opaque = 6;
330 Structured = 7;
331 Text = 8;
332 Unknown = 9;
333 }
334}
335
336define_numeric_fact_value_registry! {
337 pub enum DiagnosticFunctionKind {
339 Abs = 1;
340 Cbrt = 2;
341 Ceiling = 3;
342 Coalesce = 4;
343 CollectionContains = 5;
344 Contains = 6;
345 EndsWith = 7;
346 Exp = 8;
347 Floor = 9;
348 IsEmpty = 10;
349 IsMissing = 11;
350 IsNotEmpty = 12;
351 IsNotNull = 13;
352 IsNull = 14;
353 Left = 15;
354 Length = 16;
355 Ln = 17;
356 Log = 18;
357 Log2 = 19;
358 Log10 = 20;
359 Lower = 21;
360 Ltrim = 22;
361 Mod = 23;
362 NullIf = 24;
363 OctetLength = 25;
364 Position = 26;
365 Power = 27;
366 Replace = 28;
367 Right = 29;
368 Round = 30;
369 Rtrim = 31;
370 Sign = 32;
371 Sqrt = 33;
372 StartsWith = 34;
373 Substring = 35;
374 Trim = 36;
375 Trunc = 37;
376 Upper = 38;
377 InList = 39;
378 }
379}
380
381define_numeric_fact_value_registry! {
382 pub enum DiagnosticOperatorKind {
384 Not = 1;
385 Add = 2;
386 And = 3;
387 Div = 4;
388 Eq = 5;
389 Gt = 6;
390 Gte = 7;
391 Lt = 8;
392 Lte = 9;
393 Mul = 10;
394 Ne = 11;
395 Or = 12;
396 Sub = 13;
397 In = 14;
398 NotIn = 15;
399 Contains = 16;
400 StartsWith = 17;
401 EndsWith = 18;
402 }
403}
404
405define_numeric_fact_value_registry! {
406 pub enum DiagnosticAggregateKind {
408 Count = 1;
409 Sum = 2;
410 Avg = 3;
411 Exists = 4;
412 Min = 5;
413 Max = 6;
414 First = 7;
415 Last = 8;
416 }
417}
418
419impl DiagnosticDecodeReason {
420 #[must_use]
422 pub const fn raw(self) -> u64 {
423 match self {
424 Self::CursorEmpty => 1,
425 Self::CursorTooLong => 2,
426 Self::CursorOddLength => 3,
427 Self::CursorInvalidHex => 4,
428 Self::CursorGroupedDirectionMismatch => 5,
429 Self::CursorTokenEncode => 6,
430 Self::CursorTokenDecode => 7,
431 Self::RecoveryMarkerMagic => 8,
432 Self::RecoveryMarkerChecksum => 9,
433 Self::RecoveryMarkerState => 10,
434 }
435 }
436
437 #[must_use]
439 pub const fn known(raw: u64) -> Option<Self> {
440 match raw {
441 1 => Some(Self::CursorEmpty),
442 2 => Some(Self::CursorTooLong),
443 3 => Some(Self::CursorOddLength),
444 4 => Some(Self::CursorInvalidHex),
445 5 => Some(Self::CursorGroupedDirectionMismatch),
446 6 => Some(Self::CursorTokenEncode),
447 7 => Some(Self::CursorTokenDecode),
448 8 => Some(Self::RecoveryMarkerMagic),
449 9 => Some(Self::RecoveryMarkerChecksum),
450 10 => Some(Self::RecoveryMarkerState),
451 _ => None,
452 }
453 }
454}
455
456impl DiagnosticMutationOperation {
457 #[must_use]
459 pub const fn raw(self) -> u64 {
460 match self {
461 Self::Insert => 1,
462 Self::Replace => 2,
463 Self::Update => 3,
464 Self::Delete => 4,
465 }
466 }
467
468 #[must_use]
470 pub const fn known(raw: u64) -> Option<Self> {
471 match raw {
472 1 => Some(Self::Insert),
473 2 => Some(Self::Replace),
474 3 => Some(Self::Update),
475 4 => Some(Self::Delete),
476 _ => None,
477 }
478 }
479}
480
481impl fmt::Debug for DiagnosticFactTag {
482 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
483 write!(f, "{}", self.raw())
484 }
485}
486
487impl fmt::Debug for DiagnosticDecodeReason {
488 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
489 write!(f, "{}", self.raw())
490 }
491}
492
493impl fmt::Debug for DiagnosticMutationOperation {
494 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
495 write!(f, "{}", self.raw())
496 }
497}
498
499#[must_use]
501pub const fn pack_u32_pair(high: u32, low: u32) -> u64 {
502 (high as u64) << 32 | low as u64
503}
504
505#[must_use]
507pub const fn unpack_u32_pair(value: u64) -> (u32, u32) {
508 let bytes = value.to_be_bytes();
509 (
510 u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]),
511 u32::from_be_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]),
512 )
513}
514
515#[derive(Clone, Copy, Debug, Eq, PartialEq)]
519pub enum DiagnosticFactSchemaMismatch {
520 GlobalMaximumExceeded,
522 CodeMaximumExceeded,
524 InvalidSequence,
526 InvalidValue,
528}
529
530pub fn validate_known_diagnostic_fact_schema(
535 code: ErrorCode,
536 facts: &[(DiagnosticFactTag, u64)],
537) -> Result<(), DiagnosticFactSchemaMismatch> {
538 validate_diagnostic_fact_schema(code, facts.len(), |index| {
539 let (tag, value) = facts[index];
540 (tag.raw(), value)
541 })
542}
543
544pub fn validate_raw_diagnostic_fact_schema(
549 code: ErrorCode,
550 facts: &[(u8, u64)],
551) -> Result<(), DiagnosticFactSchemaMismatch> {
552 validate_diagnostic_fact_schema(code, facts.len(), |index| facts[index])
553}
554
555#[expect(
556 clippy::too_many_lines,
557 reason = "the frozen E-code schema registry keeps every numeric owner visible in one exhaustive dispatch"
558)]
559fn validate_diagnostic_fact_schema(
560 code: ErrorCode,
561 fact_count: usize,
562 fact_at: impl Fn(usize) -> (u8, u64),
563) -> Result<(), DiagnosticFactSchemaMismatch> {
564 if fact_count > MAX_PUBLIC_DIAGNOSTIC_FACTS {
565 return Err(DiagnosticFactSchemaMismatch::GlobalMaximumExceeded);
566 }
567
568 let maximum = diagnostic_fact_maximum(code);
569 if fact_count > maximum {
570 return Err(DiagnosticFactSchemaMismatch::CodeMaximumExceeded);
571 }
572
573 let valid_sequence = match code.raw() {
574 3 => query_plan_schema(fact_count, &fact_at),
575 6 => cursor_schema(fact_count, &fact_at),
576 16 => store_corruption_schema(fact_count, &fact_at),
577 18 => runtime_corruption_schema(fact_count, &fact_at),
578 19 => incompatible_format_schema(fact_count, &fact_at),
579 20 => runtime_invariant_schema(fact_count, &fact_at),
580 21 => runtime_conflict_schema(fact_count, &fact_at),
581 23 => runtime_unsupported_schema(fact_count, &fact_at),
582 24 => runtime_internal_schema(fact_count, &fact_at),
583 138 => tags_match(
584 fact_count,
585 &fact_at,
586 &[
587 DiagnosticFactTag::ExpectedArity,
588 DiagnosticFactTag::ActualArity,
589 ],
590 ),
591 141 | 142 | 169 | 170 => {
592 tags_match(fact_count, &fact_at, &[DiagnosticFactTag::ProjectionIndex])
593 }
594 175 => tags_match(fact_count, &fact_at, &[DiagnosticFactTag::ParameterIndex]),
595 177 | 237 => {
596 tags_match(fact_count, &fact_at, &[DiagnosticFactTag::Limit])
597 || tags_match(
598 fact_count,
599 &fact_at,
600 &[DiagnosticFactTag::ActualLength, DiagnosticFactTag::Limit],
601 )
602 }
603 178 | 180 | 202 | 203 | 205 | 236 | 269 => tags_match(
604 fact_count,
605 &fact_at,
606 &[DiagnosticFactTag::ActualCount, DiagnosticFactTag::Limit],
607 ),
608 196 | 234 => tags_match(
609 fact_count,
610 &fact_at,
611 &[
612 DiagnosticFactTag::EntityTag,
613 DiagnosticFactTag::FieldId,
614 DiagnosticFactTag::MutationOperation,
615 DiagnosticFactTag::BatchPosition,
616 ],
617 ),
618 197 => tags_match(
619 fact_count,
620 &fact_at,
621 &[
622 DiagnosticFactTag::RowLayout,
623 DiagnosticFactTag::HistoryFloor,
624 DiagnosticFactTag::CurrentLayout,
625 ],
626 ),
627 198 => tags_match(
628 fact_count,
629 &fact_at,
630 &[
631 DiagnosticFactTag::RowLayout,
632 DiagnosticFactTag::ExpectedSlotCount,
633 DiagnosticFactTag::ActualSlotCount,
634 ],
635 ),
636 201 => tags_match(
637 fact_count,
638 &fact_at,
639 &[DiagnosticFactTag::ActualCount, DiagnosticFactTag::Minimum],
640 ),
641 223 => constraint_schema(fact_count, &fact_at, true),
642 225 => constraint_schema(fact_count, &fact_at, false),
643 233 => tags_match(
644 fact_count,
645 &fact_at,
646 &[
647 DiagnosticFactTag::EntityTag,
648 DiagnosticFactTag::MutationOperation,
649 DiagnosticFactTag::BatchPosition,
650 ],
651 ),
652 235 => {
653 tags_match(fact_count, &fact_at, &[DiagnosticFactTag::ActualCount]) && fact_at(0).1 == 0
654 }
655 238 | 270 | 271 | 272 => tags_match(
656 fact_count,
657 &fact_at,
658 &[DiagnosticFactTag::ActualLength, DiagnosticFactTag::Limit],
659 ),
660 273 => tags_match(
661 fact_count,
662 &fact_at,
663 &[
664 DiagnosticFactTag::BudgetResource,
665 DiagnosticFactTag::Limit,
666 DiagnosticFactTag::Actual,
667 DiagnosticFactTag::ExecutionBudgetScope,
668 DiagnosticFactTag::ExecutionLane,
669 DiagnosticFactTag::QueryShapeFingerprintPrefix,
670 ],
671 ),
672 239 => tags_match(
673 fact_count,
674 &fact_at,
675 &[
676 DiagnosticFactTag::BatchPosition,
677 DiagnosticFactTag::ExpectedEntityTag,
678 DiagnosticFactTag::ActualEntityTag,
679 ],
680 ),
681 240 => tags_match(
682 fact_count,
683 &fact_at,
684 &[
685 DiagnosticFactTag::EntityTag,
686 DiagnosticFactTag::FirstBatchPosition,
687 DiagnosticFactTag::DuplicateBatchPosition,
688 ],
689 ),
690 _ => fact_count == 0,
691 };
692 if !valid_sequence {
693 return Err(DiagnosticFactSchemaMismatch::InvalidSequence);
694 }
695
696 for index in 0..fact_count {
697 let (raw_tag, value) = fact_at(index);
698 let Some(tag) = DiagnosticFactTag::known(raw_tag) else {
699 return Err(DiagnosticFactSchemaMismatch::InvalidSequence);
700 };
701 if !diagnostic_fact_value_is_valid(tag, value) {
702 return Err(DiagnosticFactSchemaMismatch::InvalidValue);
703 }
704 }
705 Ok(())
706}
707
708const fn diagnostic_fact_maximum(code: ErrorCode) -> usize {
709 match code.raw() {
710 6 | 16 | 18 | 24 | 197 | 198 | 233 | 239 | 240 => 3,
711 141 | 142 | 169 | 170 | 175 | 235 => 1,
712 19 | 21 | 138 | 177 | 178 | 180 | 201 | 202 | 203 | 205 | 236 | 237 | 238 | 269 | 270
713 | 271 | 272 => 2,
714 3 | 20 => 5,
715 23 | 273 => 6,
716 196 | 234 => 4,
717 223 => 73,
718 225 => 9,
719 _ => 0,
720 }
721}
722
723#[expect(
724 clippy::too_many_lines,
725 reason = "the query-plan E-code deliberately owns several exact finite fact sequences"
726)]
727fn query_plan_schema(fact_count: usize, fact_at: &impl Fn(usize) -> (u8, u64)) -> bool {
728 fact_count == 0
729 || tags_match(fact_count, fact_at, &[DiagnosticFactTag::TermIndex])
730 || tags_match(fact_count, fact_at, &[DiagnosticFactTag::ComponentIndex])
731 || tags_match(fact_count, fact_at, &[DiagnosticFactTag::GroupIndex])
732 || tags_match(fact_count, fact_at, &[DiagnosticFactTag::ClauseIndex])
733 || tags_match(fact_count, fact_at, &[DiagnosticFactTag::AggregateIndex])
734 || tags_match(fact_count, fact_at, &[DiagnosticFactTag::AggregateKind])
735 || tags_match(fact_count, fact_at, &[DiagnosticFactTag::ProjectionIndex])
736 || tags_match(
737 fact_count,
738 fact_at,
739 &[
740 DiagnosticFactTag::FirstTermIndex,
741 DiagnosticFactTag::DuplicateTermIndex,
742 ],
743 )
744 || tags_match(
745 fact_count,
746 fact_at,
747 &[
748 DiagnosticFactTag::ClauseIndex,
749 DiagnosticFactTag::OperatorKind,
750 ],
751 )
752 || tags_match(
753 fact_count,
754 fact_at,
755 &[
756 DiagnosticFactTag::AggregateIndex,
757 DiagnosticFactTag::AggregateKind,
758 ],
759 )
760 || tags_match(
761 fact_count,
762 fact_at,
763 &[
764 DiagnosticFactTag::AggregateKind,
765 DiagnosticFactTag::TypeFamily,
766 ],
767 )
768 || tags_match(
769 fact_count,
770 fact_at,
771 &[
772 DiagnosticFactTag::OperatorKind,
773 DiagnosticFactTag::TypeFamily,
774 ],
775 )
776 || tags_match(
777 fact_count,
778 fact_at,
779 &[
780 DiagnosticFactTag::BranchIndex,
781 DiagnosticFactTag::TypeFamily,
782 ],
783 )
784 || tags_match(
785 fact_count,
786 fact_at,
787 &[DiagnosticFactTag::TypeFamily, DiagnosticFactTag::TypeFamily],
788 )
789 || tags_match(
790 fact_count,
791 fact_at,
792 &[
793 DiagnosticFactTag::ClauseIndex,
794 DiagnosticFactTag::AggregateIndex,
795 DiagnosticFactTag::ActualCount,
796 ],
797 )
798 || tags_match(
799 fact_count,
800 fact_at,
801 &[
802 DiagnosticFactTag::FunctionKind,
803 DiagnosticFactTag::ExpectedArity,
804 DiagnosticFactTag::ActualArity,
805 ],
806 )
807 || tags_match(
808 fact_count,
809 fact_at,
810 &[
811 DiagnosticFactTag::FunctionKind,
812 DiagnosticFactTag::ArgumentIndex,
813 DiagnosticFactTag::TypeFamily,
814 ],
815 )
816 || tags_match(
817 fact_count,
818 fact_at,
819 &[
820 DiagnosticFactTag::OperatorKind,
821 DiagnosticFactTag::TypeFamily,
822 DiagnosticFactTag::TypeFamily,
823 ],
824 )
825 || tags_match(
826 fact_count,
827 fact_at,
828 &[
829 DiagnosticFactTag::BranchIndex,
830 DiagnosticFactTag::TypeFamily,
831 DiagnosticFactTag::TypeFamily,
832 ],
833 )
834 || tags_match(
835 fact_count,
836 fact_at,
837 &[
838 DiagnosticFactTag::TypeFamily,
839 DiagnosticFactTag::BranchIndex,
840 DiagnosticFactTag::TypeFamily,
841 ],
842 )
843 || tags_match(
844 fact_count,
845 fact_at,
846 &[
847 DiagnosticFactTag::BranchIndex,
848 DiagnosticFactTag::TypeFamily,
849 DiagnosticFactTag::BranchIndex,
850 DiagnosticFactTag::TypeFamily,
851 ],
852 )
853 || tags_match(
854 fact_count,
855 fact_at,
856 &[
857 DiagnosticFactTag::FunctionKind,
858 DiagnosticFactTag::ArgumentIndex,
859 DiagnosticFactTag::TypeFamily,
860 DiagnosticFactTag::ArgumentIndex,
861 DiagnosticFactTag::TypeFamily,
862 ],
863 )
864}
865
866fn cursor_schema(fact_count: usize, fact_at: &impl Fn(usize) -> (u8, u64)) -> bool {
867 if fact_count == 0 {
868 return true;
869 }
870 if tags_match(fact_count, fact_at, &[DiagnosticFactTag::DecodeReason]) {
871 return matches!(fact_at(0).1, 1 | 3 | 5 | 6 | 7);
872 }
873 if tags_match(
874 fact_count,
875 fact_at,
876 &[
877 DiagnosticFactTag::ActualLength,
878 DiagnosticFactTag::Maximum,
879 DiagnosticFactTag::DecodeReason,
880 ],
881 ) {
882 return fact_at(2).1 == DiagnosticDecodeReason::CursorTooLong.raw();
883 }
884 if tags_match(
885 fact_count,
886 fact_at,
887 &[
888 DiagnosticFactTag::ComponentIndex,
889 DiagnosticFactTag::DecodeReason,
890 ],
891 ) {
892 return matches!(fact_at(1).1, 4 | 6 | 7);
893 }
894 tags_match(
895 fact_count,
896 fact_at,
897 &[
898 DiagnosticFactTag::ExpectedSignaturePrefix,
899 DiagnosticFactTag::ActualSignaturePrefix,
900 ],
901 ) || tags_match(
902 fact_count,
903 fact_at,
904 &[
905 DiagnosticFactTag::ExpectedOffset,
906 DiagnosticFactTag::ActualOffset,
907 ],
908 )
909}
910
911fn store_corruption_schema(fact_count: usize, fact_at: &impl Fn(usize) -> (u8, u64)) -> bool {
912 fact_count == 0
913 || (tags_match(
914 fact_count,
915 fact_at,
916 &[
917 DiagnosticFactTag::ComponentKind,
918 DiagnosticFactTag::ActualLength,
919 DiagnosticFactTag::Limit,
920 ],
921 ) && fact_at(0).1 == DiagnosticComponentKind::CommitDataKey.raw())
922 || tags_match(
923 fact_count,
924 fact_at,
925 &[
926 DiagnosticFactTag::ExpectedEntityTag,
927 DiagnosticFactTag::ActualEntityTag,
928 ],
929 )
930}
931
932fn runtime_corruption_schema(fact_count: usize, fact_at: &impl Fn(usize) -> (u8, u64)) -> bool {
933 store_corruption_schema(fact_count, fact_at)
934 || (tags_match(fact_count, fact_at, &[DiagnosticFactTag::DecodeReason])
935 && matches!(fact_at(0).1, 8..=10))
936}
937
938fn incompatible_format_schema(fact_count: usize, fact_at: &impl Fn(usize) -> (u8, u64)) -> bool {
939 fact_count == 0
940 || tags_match(fact_count, fact_at, &[DiagnosticFactTag::ExpectedVersion])
941 || tags_match(
942 fact_count,
943 fact_at,
944 &[
945 DiagnosticFactTag::ExpectedVersion,
946 DiagnosticFactTag::ActualVersion,
947 ],
948 )
949}
950
951fn runtime_invariant_schema(fact_count: usize, fact_at: &impl Fn(usize) -> (u8, u64)) -> bool {
952 fact_count == 0
953 || (tags_match(
954 fact_count,
955 fact_at,
956 &[
957 DiagnosticFactTag::EntityTag,
958 DiagnosticFactTag::PhysicalGeneration,
959 DiagnosticFactTag::ComponentKind,
960 DiagnosticFactTag::ActualArity,
961 DiagnosticFactTag::Maximum,
962 ],
963 ) && fact_at(2).1 == DiagnosticComponentKind::IndexKey.raw())
964}
965
966fn runtime_conflict_schema(fact_count: usize, fact_at: &impl Fn(usize) -> (u8, u64)) -> bool {
967 fact_count == 0
968 || tags_match(fact_count, fact_at, &[DiagnosticFactTag::ExpectedRevision])
969 || tags_match(
970 fact_count,
971 fact_at,
972 &[
973 DiagnosticFactTag::ExpectedRevision,
974 DiagnosticFactTag::CurrentRevision,
975 ],
976 )
977}
978
979fn runtime_unsupported_schema(fact_count: usize, fact_at: &impl Fn(usize) -> (u8, u64)) -> bool {
980 fact_count == 0
981 || (tags_match(
982 fact_count,
983 fact_at,
984 &[
985 DiagnosticFactTag::EntityTag,
986 DiagnosticFactTag::PhysicalGeneration,
987 DiagnosticFactTag::ComponentIndex,
988 DiagnosticFactTag::ComponentKind,
989 DiagnosticFactTag::ActualLength,
990 DiagnosticFactTag::Limit,
991 ],
992 ) && fact_at(3).1 == DiagnosticComponentKind::IndexKeyComponent.raw())
993}
994
995fn runtime_internal_schema(fact_count: usize, fact_at: &impl Fn(usize) -> (u8, u64)) -> bool {
996 fact_count == 0
997 || tags_match(
998 fact_count,
999 fact_at,
1000 &[
1001 DiagnosticFactTag::ExpectedMemoryId,
1002 DiagnosticFactTag::ActualMemoryId,
1003 ],
1004 )
1005 || (tags_match(
1006 fact_count,
1007 fact_at,
1008 &[
1009 DiagnosticFactTag::ComponentKind,
1010 DiagnosticFactTag::ExpectedArity,
1011 DiagnosticFactTag::ActualArity,
1012 ],
1013 ) && fact_at(0).1 == DiagnosticComponentKind::RelationTargetPrimaryKey.raw())
1014}
1015
1016fn constraint_schema(
1017 fact_count: usize,
1018 fact_at: &impl Fn(usize) -> (u8, u64),
1019 allow_targeted_path: bool,
1020) -> bool {
1021 const COMMON: &[DiagnosticFactTag] = &[
1022 DiagnosticFactTag::AcceptedSchemaFingerprintMethod,
1023 DiagnosticFactTag::AcceptedSchemaFingerprintHigh,
1024 DiagnosticFactTag::AcceptedSchemaFingerprintLow,
1025 DiagnosticFactTag::EntityTag,
1026 DiagnosticFactTag::ConstraintId,
1027 DiagnosticFactTag::ConstraintKind,
1028 DiagnosticFactTag::ConstraintContext,
1029 ];
1030 if fact_count < COMMON.len()
1031 || !tags_prefix_matches(fact_count, fact_at, COMMON)
1032 || fact_at(6).1 != DiagnosticConstraintContext::WriteAdmission.raw()
1033 {
1034 return false;
1035 }
1036
1037 let constraint_kind = fact_at(5).1;
1038 if DiagnosticConstraintKind::known(constraint_kind).is_none() {
1039 return false;
1040 }
1041 let mut index = COMMON.len();
1042 if index < fact_count && fact_at(index).0 == DiagnosticFactTag::MutationOperation.raw() {
1043 index += 1;
1044 if index < fact_count && fact_at(index).0 == DiagnosticFactTag::BatchPosition.raw() {
1045 index += 1;
1046 }
1047 }
1048
1049 let path_len = fact_count - index;
1050 if path_len == 0 {
1051 return !allow_targeted_path
1052 || constraint_kind != DiagnosticConstraintKind::TargetedRule.raw();
1053 }
1054 allow_targeted_path
1055 && constraint_kind == DiagnosticConstraintKind::TargetedRule.raw()
1056 && path_len <= 64
1057 && (index..fact_count).all(|position| {
1058 DiagnosticFactTag::known(fact_at(position).0).is_some_and(is_value_path_tag)
1059 })
1060}
1061
1062fn tags_match(
1063 fact_count: usize,
1064 fact_at: &impl Fn(usize) -> (u8, u64),
1065 expected: &[DiagnosticFactTag],
1066) -> bool {
1067 fact_count == expected.len() && tags_prefix_matches(fact_count, fact_at, expected)
1068}
1069
1070fn tags_prefix_matches(
1071 fact_count: usize,
1072 fact_at: &impl Fn(usize) -> (u8, u64),
1073 expected: &[DiagnosticFactTag],
1074) -> bool {
1075 fact_count >= expected.len()
1076 && expected
1077 .iter()
1078 .enumerate()
1079 .all(|(index, tag)| fact_at(index).0 == tag.raw())
1080}
1081
1082const fn is_value_path_tag(tag: DiagnosticFactTag) -> bool {
1083 matches!(
1084 tag,
1085 DiagnosticFactTag::RootField
1086 | DiagnosticFactTag::RecordMember
1087 | DiagnosticFactTag::TupleElement
1088 | DiagnosticFactTag::Newtype
1089 | DiagnosticFactTag::EnumVariant
1090 | DiagnosticFactTag::ListElement
1091 | DiagnosticFactTag::SetElement
1092 | DiagnosticFactTag::MapEntryKey
1093 | DiagnosticFactTag::MapEntryValue
1094 )
1095}
1096
1097const fn diagnostic_fact_value_is_valid(tag: DiagnosticFactTag, value: u64) -> bool {
1098 match tag {
1099 DiagnosticFactTag::AcceptedSchemaFingerprintMethod
1100 | DiagnosticFactTag::ExpectedMemoryId
1101 | DiagnosticFactTag::ActualMemoryId => value <= u8::MAX as u64,
1102 DiagnosticFactTag::ConstraintId
1103 | DiagnosticFactTag::FieldId
1104 | DiagnosticFactTag::IndexId
1105 | DiagnosticFactTag::RelationId
1106 | DiagnosticFactTag::BatchPosition
1107 | DiagnosticFactTag::FirstBatchPosition
1108 | DiagnosticFactTag::DuplicateBatchPosition
1109 | DiagnosticFactTag::RowLayout
1110 | DiagnosticFactTag::HistoryFloor
1111 | DiagnosticFactTag::CurrentLayout
1112 | DiagnosticFactTag::RootField
1113 | DiagnosticFactTag::Newtype
1114 | DiagnosticFactTag::ListElement
1115 | DiagnosticFactTag::SetElement
1116 | DiagnosticFactTag::MapEntryKey
1117 | DiagnosticFactTag::MapEntryValue => value <= u32::MAX as u64,
1118 DiagnosticFactTag::ConstraintKind => DiagnosticConstraintKind::known(value).is_some(),
1119 DiagnosticFactTag::ConstraintContext => DiagnosticConstraintContext::known(value).is_some(),
1120 DiagnosticFactTag::TypeFamily => DiagnosticTypeFamily::known(value).is_some(),
1121 DiagnosticFactTag::FunctionKind => DiagnosticFunctionKind::known(value).is_some(),
1122 DiagnosticFactTag::OperatorKind => DiagnosticOperatorKind::known(value).is_some(),
1123 DiagnosticFactTag::AggregateKind => DiagnosticAggregateKind::known(value).is_some(),
1124 DiagnosticFactTag::ComponentKind => DiagnosticComponentKind::known(value).is_some(),
1125 DiagnosticFactTag::DecodeReason => DiagnosticDecodeReason::known(value).is_some(),
1126 DiagnosticFactTag::BudgetResource => {
1127 DiagnosticExecutionBudgetResource::known(value).is_some()
1128 }
1129 DiagnosticFactTag::ExecutionBudgetScope => {
1130 DiagnosticExecutionBudgetScope::known(value).is_some()
1131 }
1132 DiagnosticFactTag::ExecutionLane => DiagnosticExecutionLane::known(value).is_some(),
1133 DiagnosticFactTag::MutationOperation => DiagnosticMutationOperation::known(value).is_some(),
1134 _ => true,
1135 }
1136}
1137
1138#[cfg(test)]
1139mod tests {
1140 use super::{
1141 DiagnosticAggregateKind, DiagnosticComponentKind, DiagnosticConstraintContext,
1142 DiagnosticConstraintKind, DiagnosticDecodeReason, DiagnosticExecutionBudgetResource,
1143 DiagnosticExecutionBudgetScope, DiagnosticExecutionLane, DiagnosticFactSchemaMismatch,
1144 DiagnosticFactTag, DiagnosticFunctionKind, DiagnosticMutationOperation,
1145 DiagnosticOperatorKind, DiagnosticTypeFamily, ORDERED_FACT_TAGS, pack_u32_pair,
1146 unpack_u32_pair, validate_known_diagnostic_fact_schema,
1147 validate_raw_diagnostic_fact_schema,
1148 };
1149 use crate::ErrorCode;
1150
1151 #[test]
1152 fn fact_tag_registry_is_fixed_unique_and_contiguous() {
1153 for (index, tag) in ORDERED_FACT_TAGS.iter().copied().enumerate() {
1154 let expected = u8::try_from(index + 1).expect("fact-tag index fits u8");
1155 assert_eq!(tag.raw(), expected);
1156 assert_eq!(DiagnosticFactTag::known(expected), Some(tag));
1157 }
1158
1159 assert_eq!(DiagnosticFactTag::known(0), None);
1160 assert_eq!(DiagnosticFactTag::known(93), None);
1161 assert_eq!(DiagnosticFactTag::known(u8::MAX), None);
1162 }
1163
1164 #[test]
1165 fn execution_budget_fact_value_registries_are_fixed() {
1166 for (index, resource) in DiagnosticExecutionBudgetResource::ALL
1167 .iter()
1168 .copied()
1169 .enumerate()
1170 {
1171 let expected = u64::try_from(index + 1).expect("resource index fits u64");
1172 assert_eq!(resource.raw(), expected);
1173 assert_eq!(
1174 DiagnosticExecutionBudgetResource::known(expected),
1175 Some(resource)
1176 );
1177 }
1178 assert_eq!(DiagnosticExecutionBudgetResource::known(0), None);
1179 assert_eq!(DiagnosticExecutionBudgetResource::known(22), None);
1180
1181 assert_eq!(DiagnosticExecutionBudgetScope::Execution.raw(), 1);
1182 assert_eq!(DiagnosticExecutionBudgetScope::Request.raw(), 2);
1183 assert_eq!(DiagnosticExecutionBudgetScope::known(3), None);
1184
1185 assert_eq!(DiagnosticExecutionLane::PublicRead.raw(), 1);
1186 assert_eq!(DiagnosticExecutionLane::TrustedRead.raw(), 2);
1187 assert_eq!(DiagnosticExecutionLane::Diagnostic.raw(), 3);
1188 assert_eq!(DiagnosticExecutionLane::Mutation.raw(), 4);
1189 assert_eq!(DiagnosticExecutionLane::Recovery.raw(), 5);
1190 assert_eq!(DiagnosticExecutionLane::known(6), None);
1191 }
1192
1193 #[test]
1194 fn accepted_identity_pair_packing_is_exact() {
1195 for pair in [
1196 (0, 0),
1197 (1, 2),
1198 (u32::MAX, 0),
1199 (0, u32::MAX),
1200 (u32::MAX, u32::MAX),
1201 ] {
1202 assert_eq!(unpack_u32_pair(pack_u32_pair(pair.0, pair.1)), pair);
1203 }
1204 }
1205
1206 #[test]
1207 fn constraint_fact_value_registries_are_fixed() {
1208 assert_eq!(DiagnosticConstraintKind::Check.raw(), 1);
1209 assert_eq!(DiagnosticConstraintKind::NotNull.raw(), 2);
1210 assert_eq!(DiagnosticConstraintKind::Relation.raw(), 3);
1211 assert_eq!(DiagnosticConstraintKind::TargetedRule.raw(), 4);
1212 assert_eq!(DiagnosticConstraintKind::Unique.raw(), 5);
1213 assert_eq!(DiagnosticConstraintKind::known(0), None);
1214 assert_eq!(DiagnosticConstraintKind::known(6), None);
1215
1216 assert_eq!(DiagnosticConstraintContext::Integrity.raw(), 1);
1217 assert_eq!(DiagnosticConstraintContext::MigrationValidation.raw(), 2);
1218 assert_eq!(DiagnosticConstraintContext::WriteAdmission.raw(), 3);
1219 assert_eq!(DiagnosticConstraintContext::known(0), None);
1220 assert_eq!(DiagnosticConstraintContext::known(4), None);
1221 }
1222
1223 #[test]
1224 fn component_kind_registry_is_fixed_and_numeric() {
1225 let kinds = [
1226 DiagnosticComponentKind::CommitDataKey,
1227 DiagnosticComponentKind::IndexKey,
1228 DiagnosticComponentKind::IndexKeyComponent,
1229 DiagnosticComponentKind::RelationTargetPrimaryKey,
1230 ];
1231
1232 for (index, kind) in kinds.iter().copied().enumerate() {
1233 let expected = (index + 1) as u64;
1234 assert_eq!(kind.raw(), expected);
1235 assert_eq!(DiagnosticComponentKind::known(expected), Some(kind));
1236 assert_eq!(format!("{kind:?}"), expected.to_string());
1237 }
1238 assert_eq!(DiagnosticComponentKind::known(0), None);
1239 assert_eq!(DiagnosticComponentKind::known(5), None);
1240 }
1241
1242 #[test]
1243 fn decode_reason_registry_is_fixed_and_numeric() {
1244 let reasons = [
1245 DiagnosticDecodeReason::CursorEmpty,
1246 DiagnosticDecodeReason::CursorTooLong,
1247 DiagnosticDecodeReason::CursorOddLength,
1248 DiagnosticDecodeReason::CursorInvalidHex,
1249 DiagnosticDecodeReason::CursorGroupedDirectionMismatch,
1250 DiagnosticDecodeReason::CursorTokenEncode,
1251 DiagnosticDecodeReason::CursorTokenDecode,
1252 DiagnosticDecodeReason::RecoveryMarkerMagic,
1253 DiagnosticDecodeReason::RecoveryMarkerChecksum,
1254 DiagnosticDecodeReason::RecoveryMarkerState,
1255 ];
1256
1257 for (index, reason) in reasons.iter().copied().enumerate() {
1258 let expected = (index + 1) as u64;
1259 assert_eq!(reason.raw(), expected);
1260 assert_eq!(DiagnosticDecodeReason::known(expected), Some(reason));
1261 assert_eq!(format!("{reason:?}"), expected.to_string());
1262 }
1263
1264 assert_eq!(DiagnosticDecodeReason::known(0), None);
1265 assert_eq!(DiagnosticDecodeReason::known(11), None);
1266 }
1267
1268 #[test]
1269 fn mutation_operation_registry_is_fixed_and_numeric() {
1270 let operations = [
1271 DiagnosticMutationOperation::Insert,
1272 DiagnosticMutationOperation::Replace,
1273 DiagnosticMutationOperation::Update,
1274 DiagnosticMutationOperation::Delete,
1275 ];
1276
1277 for (index, operation) in operations.iter().copied().enumerate() {
1278 let expected = (index + 1) as u64;
1279 assert_eq!(operation.raw(), expected);
1280 assert_eq!(
1281 DiagnosticMutationOperation::known(expected),
1282 Some(operation)
1283 );
1284 assert_eq!(format!("{operation:?}"), expected.to_string());
1285 }
1286
1287 assert_eq!(DiagnosticMutationOperation::known(0), None);
1288 assert_eq!(DiagnosticMutationOperation::known(5), None);
1289 }
1290
1291 #[test]
1292 fn query_kind_registries_are_fixed_contiguous_and_numeric() {
1293 for raw in 1..=9 {
1294 let value = DiagnosticTypeFamily::known(raw).expect("type family should be known");
1295 assert_eq!(value.raw(), raw);
1296 assert_eq!(format!("{value:?}"), raw.to_string());
1297 }
1298 assert_eq!(DiagnosticTypeFamily::known(0), None);
1299 assert_eq!(DiagnosticTypeFamily::known(10), None);
1300
1301 for raw in 1..=39 {
1302 let value = DiagnosticFunctionKind::known(raw).expect("function kind should be known");
1303 assert_eq!(value.raw(), raw);
1304 assert_eq!(format!("{value:?}"), raw.to_string());
1305 }
1306 assert_eq!(DiagnosticFunctionKind::known(0), None);
1307 assert_eq!(DiagnosticFunctionKind::known(40), None);
1308
1309 for raw in 1..=18 {
1310 let value = DiagnosticOperatorKind::known(raw).expect("operator kind should be known");
1311 assert_eq!(value.raw(), raw);
1312 assert_eq!(format!("{value:?}"), raw.to_string());
1313 }
1314 assert_eq!(DiagnosticOperatorKind::known(0), None);
1315 assert_eq!(DiagnosticOperatorKind::known(19), None);
1316
1317 for raw in 1..=8 {
1318 let value =
1319 DiagnosticAggregateKind::known(raw).expect("aggregate kind should be known");
1320 assert_eq!(value.raw(), raw);
1321 assert_eq!(format!("{value:?}"), raw.to_string());
1322 }
1323 assert_eq!(DiagnosticAggregateKind::known(0), None);
1324 assert_eq!(DiagnosticAggregateKind::known(9), None);
1325 }
1326
1327 #[test]
1328 fn per_code_schema_rejects_missing_disallowed_and_noncanonical_tags() {
1329 let valid = [
1330 (DiagnosticFactTag::ActualCount, 5),
1331 (DiagnosticFactTag::Limit, 4),
1332 ];
1333 assert_eq!(
1334 validate_known_diagnostic_fact_schema(
1335 ErrorCode::RUNTIME_BOUNDARY_MUTATION_BATCH_TOO_MANY_ITEMS,
1336 &valid,
1337 ),
1338 Ok(())
1339 );
1340 assert_eq!(
1341 validate_known_diagnostic_fact_schema(
1342 ErrorCode::RUNTIME_BOUNDARY_MUTATION_BATCH_TOO_MANY_ITEMS,
1343 &valid[..1],
1344 ),
1345 Err(DiagnosticFactSchemaMismatch::InvalidSequence)
1346 );
1347 assert_eq!(
1348 validate_known_diagnostic_fact_schema(
1349 ErrorCode::RUNTIME_BOUNDARY_MUTATION_BATCH_TOO_MANY_ITEMS,
1350 &[valid[1], valid[0]],
1351 ),
1352 Err(DiagnosticFactSchemaMismatch::InvalidSequence)
1353 );
1354 assert_eq!(
1355 validate_known_diagnostic_fact_schema(ErrorCode::QUERY_VALIDATE, &valid),
1356 Err(DiagnosticFactSchemaMismatch::CodeMaximumExceeded)
1357 );
1358 }
1359
1360 #[test]
1361 fn execution_budget_schema_requires_complete_typed_attribution() {
1362 let valid = [
1363 (
1364 DiagnosticFactTag::BudgetResource,
1365 DiagnosticExecutionBudgetResource::StoredBytesRead.raw(),
1366 ),
1367 (DiagnosticFactTag::Limit, 4_096),
1368 (DiagnosticFactTag::Actual, 4_097),
1369 (
1370 DiagnosticFactTag::ExecutionBudgetScope,
1371 DiagnosticExecutionBudgetScope::Request.raw(),
1372 ),
1373 (
1374 DiagnosticFactTag::ExecutionLane,
1375 DiagnosticExecutionLane::TrustedRead.raw(),
1376 ),
1377 (DiagnosticFactTag::QueryShapeFingerprintPrefix, 17),
1378 ];
1379 assert_eq!(
1380 validate_known_diagnostic_fact_schema(
1381 ErrorCode::RUNTIME_BOUNDARY_EXECUTION_BUDGET_EXCEEDED,
1382 &valid,
1383 ),
1384 Ok(())
1385 );
1386 assert_eq!(
1387 validate_known_diagnostic_fact_schema(
1388 ErrorCode::RUNTIME_BOUNDARY_EXECUTION_BUDGET_EXCEEDED,
1389 &valid[..5],
1390 ),
1391 Err(DiagnosticFactSchemaMismatch::InvalidSequence)
1392 );
1393
1394 let mut invalid_resource = valid;
1395 invalid_resource[0].1 = 0;
1396 assert_eq!(
1397 validate_known_diagnostic_fact_schema(
1398 ErrorCode::RUNTIME_BOUNDARY_EXECUTION_BUDGET_EXCEEDED,
1399 &invalid_resource,
1400 ),
1401 Err(DiagnosticFactSchemaMismatch::InvalidValue)
1402 );
1403 }
1404
1405 #[test]
1406 fn raw_schema_keeps_unknown_context_numeric_but_marks_it_invalid() {
1407 assert_eq!(
1408 validate_raw_diagnostic_fact_schema(
1409 ErrorCode::QUERY_INVALID_CONTINUATION_CURSOR,
1410 &[(u8::MAX, 17)],
1411 ),
1412 Err(DiagnosticFactSchemaMismatch::InvalidSequence)
1413 );
1414 assert_eq!(
1415 validate_raw_diagnostic_fact_schema(
1416 ErrorCode::QUERY_INVALID_CONTINUATION_CURSOR,
1417 &[(DiagnosticFactTag::DecodeReason.raw(), u64::MAX)],
1418 ),
1419 Err(DiagnosticFactSchemaMismatch::InvalidSequence)
1420 );
1421 }
1422
1423 #[test]
1424 fn constraint_schema_enforces_authority_operation_and_bounded_path_suffix() {
1425 let mut targeted = vec![
1426 (DiagnosticFactTag::AcceptedSchemaFingerprintMethod, 1),
1427 (DiagnosticFactTag::AcceptedSchemaFingerprintHigh, 2),
1428 (DiagnosticFactTag::AcceptedSchemaFingerprintLow, 3),
1429 (DiagnosticFactTag::EntityTag, 17),
1430 (DiagnosticFactTag::ConstraintId, 4),
1431 (
1432 DiagnosticFactTag::ConstraintKind,
1433 DiagnosticConstraintKind::TargetedRule.raw(),
1434 ),
1435 (
1436 DiagnosticFactTag::ConstraintContext,
1437 DiagnosticConstraintContext::WriteAdmission.raw(),
1438 ),
1439 (
1440 DiagnosticFactTag::MutationOperation,
1441 DiagnosticMutationOperation::Insert.raw(),
1442 ),
1443 (DiagnosticFactTag::BatchPosition, 0),
1444 ];
1445 targeted.extend((0..64).map(|index| (DiagnosticFactTag::ListElement, index)));
1446 assert_eq!(targeted.len(), 73);
1447 assert_eq!(
1448 validate_known_diagnostic_fact_schema(
1449 ErrorCode::RUNTIME_BOUNDARY_CONSTRAINT_VIOLATION,
1450 targeted.as_slice(),
1451 ),
1452 Ok(())
1453 );
1454
1455 let mut overlong = targeted.clone();
1456 overlong.push((DiagnosticFactTag::ListElement, 64));
1457 assert_eq!(
1458 validate_known_diagnostic_fact_schema(
1459 ErrorCode::RUNTIME_BOUNDARY_CONSTRAINT_VIOLATION,
1460 overlong.as_slice(),
1461 ),
1462 Err(DiagnosticFactSchemaMismatch::CodeMaximumExceeded)
1463 );
1464
1465 let mut non_targeted_path = targeted;
1466 non_targeted_path[5].1 = DiagnosticConstraintKind::Unique.raw();
1467 assert_eq!(
1468 validate_known_diagnostic_fact_schema(
1469 ErrorCode::RUNTIME_BOUNDARY_CONSTRAINT_VIOLATION,
1470 non_targeted_path.as_slice(),
1471 ),
1472 Err(DiagnosticFactSchemaMismatch::InvalidSequence)
1473 );
1474 }
1475
1476 #[test]
1477 fn schema_enforces_global_ceiling_before_per_code_ceiling() {
1478 let facts = vec![(DiagnosticFactTag::ActualCount.raw(), 0); 81];
1479 assert_eq!(
1480 validate_raw_diagnostic_fact_schema(ErrorCode::QUERY_PLAN, facts.as_slice()),
1481 Err(DiagnosticFactSchemaMismatch::GlobalMaximumExceeded)
1482 );
1483 }
1484}