Skip to main content

icydb_diagnostic_code/
lib.rs

1//! Module: lib
2//! Responsibility: compact diagnostic identity and public numeric error-code mapping.
3//! Does not own: rich diagnostic prose, Candid wire types, or runtime error construction.
4//! Boundary: maps rich internal diagnostic categories to stable compact public codes.
5//!
6//! This crate intentionally contains no rich diagnostic prose or Candid wire
7//! types. Production canister builds collapse diagnostics to numeric wire
8//! codes before they cross the public canister boundary. `Debug` output is
9//! numeric for the same reason: host tooling can recover labels from the code
10//! table without making every wasm canister retain those labels.
11
12use std::fmt;
13
14mod fact;
15mod query_field;
16
17pub use fact::{
18    DiagnosticAggregateKind, DiagnosticBacklogResource, DiagnosticComponentKind,
19    DiagnosticConstraintContext, DiagnosticConstraintKind, DiagnosticDecodeReason,
20    DiagnosticExecutionBudgetResource, DiagnosticExecutionBudgetScope, DiagnosticExecutionLane,
21    DiagnosticFactSchemaMismatch, DiagnosticFactTag, DiagnosticFunctionKind,
22    DiagnosticMutationOperation, DiagnosticOperatorKind, DiagnosticTypeFamily,
23    MAX_PUBLIC_DIAGNOSTIC_FACTS, pack_u32_pair, unpack_u32_pair,
24    validate_known_diagnostic_fact_schema, validate_raw_diagnostic_fact_schema,
25};
26pub use query_field::{
27    MAX_PUBLIC_QUERY_FIELD_BYTES, QueryFieldRole, QueryFieldSchemaMismatch,
28    validate_query_field_schema,
29};
30
31///
32/// DiagnosticCode
33///
34/// Stable machine-readable diagnostic reason.
35///
36
37#[remain::sorted]
38#[derive(Clone, Copy, Eq, Hash, PartialEq)]
39pub enum DiagnosticCode {
40    /// An admitted exact count has no available exact-cardinality metadata.
41    QueryExactCountMetadataUnavailable,
42    QueryIntent,
43    QueryInvalidContinuationCursor,
44    QueryNotFound,
45    QueryNotUnique,
46    QueryNumericNotRepresentable,
47    QueryNumericOverflow,
48    QueryPlan,
49    QueryReadAdmission,
50    QuerySqlSurfaceMismatch,
51    QuerySqlWriteBoundary,
52    QueryUnknownAggregateTargetField,
53    QueryUnorderedPagination,
54    QueryUnsupportedProjection,
55    QueryUnsupportedSqlFeature,
56    QueryValidate,
57    RuntimeConflict,
58    RuntimeCorruption,
59    RuntimeIncompatiblePersistedFormat,
60    RuntimeInternal,
61    RuntimeInvariantViolation,
62    RuntimeNotFound,
63    RuntimeUnsupported,
64    SchemaDdlAdmission,
65    StoreCorruption,
66    StoreInvariantViolation,
67    StoreNotFound,
68}
69
70impl DiagnosticCode {
71    /// Return the broad diagnostic class for this code.
72    #[must_use]
73    pub const fn class(self) -> ErrorClass {
74        match self {
75            Self::StoreCorruption | Self::RuntimeCorruption => ErrorClass::Corruption,
76            Self::RuntimeIncompatiblePersistedFormat => ErrorClass::IncompatiblePersistedFormat,
77            Self::QueryNotFound | Self::StoreNotFound | Self::RuntimeNotFound => {
78                ErrorClass::NotFound
79            }
80            Self::RuntimeConflict => ErrorClass::Conflict,
81            Self::QueryExactCountMetadataUnavailable
82            | Self::QueryUnsupportedSqlFeature
83            | Self::QueryUnknownAggregateTargetField
84            | Self::QueryUnsupportedProjection
85            | Self::QuerySqlSurfaceMismatch
86            | Self::QuerySqlWriteBoundary
87            | Self::RuntimeUnsupported => ErrorClass::Unsupported,
88            Self::StoreInvariantViolation | Self::RuntimeInvariantViolation => {
89                ErrorClass::InvariantViolation
90            }
91            Self::RuntimeInternal => ErrorClass::Internal,
92            Self::QueryValidate
93            | Self::QueryIntent
94            | Self::QueryPlan
95            | Self::QueryReadAdmission
96            | Self::QueryUnorderedPagination
97            | Self::QueryInvalidContinuationCursor
98            | Self::QueryNotUnique
99            | Self::QueryNumericOverflow
100            | Self::QueryNumericNotRepresentable
101            | Self::SchemaDdlAdmission => ErrorClass::Query,
102        }
103    }
104
105    /// Return the default diagnostic origin for this code.
106    #[must_use]
107    pub const fn origin(self) -> ErrorOrigin {
108        match self {
109            Self::StoreNotFound | Self::StoreCorruption | Self::StoreInvariantViolation => {
110                ErrorOrigin::Store
111            }
112            Self::RuntimeCorruption
113            | Self::RuntimeIncompatiblePersistedFormat
114            | Self::RuntimeInvariantViolation
115            | Self::RuntimeConflict
116            | Self::RuntimeNotFound
117            | Self::RuntimeUnsupported
118            | Self::RuntimeInternal => ErrorOrigin::Runtime,
119            Self::QueryExactCountMetadataUnavailable
120            | Self::QueryValidate
121            | Self::QueryIntent
122            | Self::QueryPlan
123            | Self::QueryReadAdmission
124            | Self::QueryUnorderedPagination
125            | Self::QueryInvalidContinuationCursor
126            | Self::QueryNotFound
127            | Self::QueryNotUnique
128            | Self::QueryNumericOverflow
129            | Self::QueryNumericNotRepresentable
130            | Self::QueryUnknownAggregateTargetField
131            | Self::QueryUnsupportedProjection
132            | Self::QueryUnsupportedSqlFeature
133            | Self::QuerySqlSurfaceMismatch
134            | Self::QuerySqlWriteBoundary
135            | Self::SchemaDdlAdmission => ErrorOrigin::Query,
136        }
137    }
138
139    /// Return the compact public wire code for this broad diagnostic reason.
140    #[must_use]
141    pub const fn error_code(self) -> ErrorCode {
142        match self {
143            Self::QueryExactCountMetadataUnavailable => {
144                ErrorCode::QUERY_EXACT_COUNT_METADATA_UNAVAILABLE
145            }
146            Self::QueryValidate => ErrorCode::QUERY_VALIDATE,
147            Self::QueryIntent => ErrorCode::QUERY_INTENT,
148            Self::QueryPlan => ErrorCode::QUERY_PLAN,
149            Self::QueryReadAdmission => ErrorCode::QUERY_READ_ADMISSION,
150            Self::QueryUnorderedPagination => ErrorCode::QUERY_UNORDERED_PAGINATION,
151            Self::QueryInvalidContinuationCursor => ErrorCode::QUERY_INVALID_CONTINUATION_CURSOR,
152            Self::QueryNotFound => ErrorCode::QUERY_NOT_FOUND,
153            Self::QueryNotUnique => ErrorCode::QUERY_NOT_UNIQUE,
154            Self::QueryNumericOverflow => ErrorCode::QUERY_NUMERIC_OVERFLOW,
155            Self::QueryNumericNotRepresentable => ErrorCode::QUERY_NUMERIC_NOT_REPRESENTABLE,
156            Self::QueryUnknownAggregateTargetField => {
157                ErrorCode::QUERY_UNKNOWN_AGGREGATE_TARGET_FIELD
158            }
159            Self::QueryUnsupportedProjection => ErrorCode::QUERY_UNSUPPORTED_PROJECTION,
160            Self::QueryUnsupportedSqlFeature => ErrorCode::QUERY_UNSUPPORTED_SQL_FEATURE,
161            Self::QuerySqlSurfaceMismatch => ErrorCode::QUERY_SQL_SURFACE_MISMATCH,
162            Self::QuerySqlWriteBoundary => ErrorCode::QUERY_SQL_WRITE_BOUNDARY,
163            Self::SchemaDdlAdmission => ErrorCode::SCHEMA_DDL_ADMISSION,
164            Self::StoreNotFound => ErrorCode::STORE_NOT_FOUND,
165            Self::StoreCorruption => ErrorCode::STORE_CORRUPTION,
166            Self::StoreInvariantViolation => ErrorCode::STORE_INVARIANT_VIOLATION,
167            Self::RuntimeCorruption => ErrorCode::RUNTIME_CORRUPTION,
168            Self::RuntimeIncompatiblePersistedFormat => {
169                ErrorCode::RUNTIME_INCOMPATIBLE_PERSISTED_FORMAT
170            }
171            Self::RuntimeInvariantViolation => ErrorCode::RUNTIME_INVARIANT_VIOLATION,
172            Self::RuntimeConflict => ErrorCode::RUNTIME_CONFLICT,
173            Self::RuntimeNotFound => ErrorCode::RUNTIME_NOT_FOUND,
174            Self::RuntimeUnsupported => ErrorCode::RUNTIME_UNSUPPORTED,
175            Self::RuntimeInternal => ErrorCode::RUNTIME_INTERNAL,
176        }
177    }
178}
179
180impl fmt::Debug for DiagnosticCode {
181    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
182        fmt_compact_code(f, self.error_code().raw())
183    }
184}
185
186///
187/// ErrorCode
188///
189/// Stable numeric public error identity.
190///
191/// The public Candid `icydb::Error` stores this value as `nat16` so canister
192/// interfaces do not retain rich diagnostic enum labels. Rich diagnostics can
193/// still be reconstructed by host-side tooling from this leaf code. Before
194/// 1.0.0, the code space is hard-cut to a single compact sequential range.
195///
196
197#[derive(Clone, Copy, Eq, Hash, PartialEq)]
198pub struct ErrorCode(u16);
199
200mod registry;
201
202impl ErrorCode {
203    /// Build an error code from its raw public wire value.
204    #[must_use]
205    pub const fn from_raw(raw: u16) -> Self {
206        Self(raw)
207    }
208
209    /// Return the raw public wire value.
210    #[must_use]
211    pub const fn raw(self) -> u16 {
212        self.0
213    }
214}
215
216impl fmt::Debug for ErrorCode {
217    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
218        fmt_compact_code(f, self.raw())
219    }
220}
221
222///
223/// ErrorClass
224///
225/// Broad diagnostic class used for recovery decisions.
226///
227
228#[remain::sorted]
229#[derive(Clone, Copy, Eq, Hash, PartialEq)]
230pub enum ErrorClass {
231    Conflict,
232    Corruption,
233    IncompatiblePersistedFormat,
234    Internal,
235    InvariantViolation,
236    NotFound,
237    Query,
238    Unsupported,
239}
240
241impl ErrorClass {
242    /// Return the compact public wire code for this diagnostic class.
243    #[must_use]
244    pub const fn wire_code(self) -> u8 {
245        match self {
246            Self::Query => 1,
247            Self::Corruption => 2,
248            Self::IncompatiblePersistedFormat => 3,
249            Self::NotFound => 4,
250            Self::Internal => 5,
251            Self::Conflict => 6,
252            Self::Unsupported => 7,
253            Self::InvariantViolation => 8,
254        }
255    }
256
257    /// Recover a diagnostic class from its compact public wire code.
258    #[must_use]
259    pub const fn from_wire_code(code: u8) -> Option<Self> {
260        match code {
261            1 => Some(Self::Query),
262            2 => Some(Self::Corruption),
263            3 => Some(Self::IncompatiblePersistedFormat),
264            4 => Some(Self::NotFound),
265            5 => Some(Self::Internal),
266            6 => Some(Self::Conflict),
267            7 => Some(Self::Unsupported),
268            8 => Some(Self::InvariantViolation),
269            _ => None,
270        }
271    }
272}
273
274impl fmt::Debug for ErrorClass {
275    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
276        fmt_compact_code(f, u16::from(self.wire_code()))
277    }
278}
279
280///
281/// ErrorOrigin
282///
283/// Subsystem that owns the diagnostic.
284///
285
286#[remain::sorted]
287#[derive(Clone, Copy, Eq, Hash, PartialEq)]
288pub enum ErrorOrigin {
289    Cursor,
290    Executor,
291    Identity,
292    Index,
293    Interface,
294    Planner,
295    Query,
296    Recovery,
297    Response,
298    Runtime,
299    Serialize,
300    Store,
301}
302
303impl ErrorOrigin {
304    /// Return the compact public wire code for this diagnostic origin.
305    #[must_use]
306    pub const fn wire_code(self) -> u8 {
307        match self {
308            Self::Cursor => 1,
309            Self::Executor => 2,
310            Self::Identity => 3,
311            Self::Index => 4,
312            Self::Interface => 5,
313            Self::Planner => 6,
314            Self::Query => 7,
315            Self::Recovery => 8,
316            Self::Response => 9,
317            Self::Runtime => 10,
318            Self::Serialize => 11,
319            Self::Store => 12,
320        }
321    }
322
323    /// Recover a known diagnostic origin from its compact public wire code.
324    #[must_use]
325    pub const fn from_known_wire_code(code: u8) -> Option<Self> {
326        match code {
327            1 => Some(Self::Cursor),
328            2 => Some(Self::Executor),
329            3 => Some(Self::Identity),
330            4 => Some(Self::Index),
331            5 => Some(Self::Interface),
332            6 => Some(Self::Planner),
333            7 => Some(Self::Query),
334            8 => Some(Self::Recovery),
335            9 => Some(Self::Response),
336            10 => Some(Self::Runtime),
337            11 => Some(Self::Serialize),
338            12 => Some(Self::Store),
339            _ => None,
340        }
341    }
342
343    /// Recover a diagnostic origin from its compact public wire code.
344    ///
345    /// Unknown origin codes fail closed to `Runtime`, matching the public
346    /// boundary behavior used by the Candid facade.
347    #[must_use]
348    pub const fn from_wire_code(code: u8) -> Self {
349        match Self::from_known_wire_code(code) {
350            Some(origin) => origin,
351            None => Self::Runtime,
352        }
353    }
354}
355
356impl fmt::Debug for ErrorOrigin {
357    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
358        fmt_compact_code(f, u16::from(self.wire_code()))
359    }
360}
361
362///
363/// QueryErrorKind
364///
365/// Public query error category.
366///
367
368#[repr(u16)]
369#[derive(Clone, Copy, Eq, Hash, PartialEq)]
370pub enum QueryErrorKind {
371    Validate,
372    Intent,
373    Plan,
374    UnorderedPagination,
375    InvalidContinuationCursor,
376    NotFound,
377    NotUnique,
378}
379
380impl fmt::Debug for QueryErrorKind {
381    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
382        fmt_compact_code(f, *self as u16)
383    }
384}
385
386///
387/// QueryProjectionCode
388///
389/// Compact query projection admission/runtime identifier.
390/// Variant order is wire-order significant for public error-code offsets.
391///
392
393#[repr(u16)]
394#[derive(Clone, Copy, Eq, Hash, PartialEq)]
395pub enum QueryProjectionCode {
396    NumericLiteralRequired,
397    NumericScaleArguments,
398    NestedFieldPathPreview,
399    CaseConditionBooleanRequired,
400    NumericInputRequired,
401    TextOrBlobInputRequired,
402    TextInputRequired,
403    TextOrNullArgumentRequired,
404    IntegerOrNullArgumentRequired,
405    UnaryOperandIncompatible,
406    BinaryOperandsIncompatible,
407}
408
409impl fmt::Debug for QueryProjectionCode {
410    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
411        fmt_compact_code(f, *self as u16)
412    }
413}
414
415///
416/// QueryReadAdmissionCode
417///
418/// Compact read-admission rejection identifier.
419/// Variant order is wire-order significant for public error-code offsets.
420///
421
422#[repr(u16)]
423#[derive(Clone, Copy, Eq, Hash, PartialEq)]
424pub enum QueryReadAdmissionCode {
425    PublicQueryRequiresLimit,
426    PublicQueryRequiresIndex,
427    UnboundedFullScanRejected,
428    SortRequiresMaterialization,
429    GroupedQueryRequiresLimits,
430    GroupedQueryExceedsBudget,
431    DiagnosticLaneDoesNotExecute,
432    ReturnedRowBoundExceedsPolicy,
433    PrimaryKeyInputExceedsPolicy,
434    /// Authored query expressions or values exceed the shared depth ceiling.
435    InputDepthExceeded,
436    /// Authored query components exceed the shared node ceiling.
437    InputNodesExceeded,
438    /// Authored query payload exceeds the shared byte ceiling.
439    InputBytesExceeded,
440    /// Logical explain describes a request before any cursor boundary.
441    ExplainDoesNotAcceptCursor,
442}
443
444impl fmt::Debug for QueryReadAdmissionCode {
445    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
446        fmt_compact_code(f, *self as u16)
447    }
448}
449
450///
451/// RuntimeErrorKind
452///
453/// Public runtime error category.
454///
455
456#[repr(u16)]
457#[derive(Clone, Copy, Eq, Hash, PartialEq)]
458pub enum RuntimeErrorKind {
459    Corruption,
460    IncompatiblePersistedFormat,
461    InvariantViolation,
462    Conflict,
463    NotFound,
464    Unsupported,
465    Internal,
466}
467
468impl fmt::Debug for RuntimeErrorKind {
469    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
470        fmt_compact_code(f, *self as u16)
471    }
472}
473
474///
475/// RuntimeBoundaryCode
476///
477/// Compact public-runtime boundary identifier.
478/// Variant order is wire-order significant for public error-code offsets.
479///
480
481#[repr(u16)]
482#[derive(Clone, Copy, Eq, Hash, PartialEq)]
483pub enum RuntimeBoundaryCode {
484    SqlSurfaceControllerRequired,
485    SchemaSurfaceControllerRequired,
486    SqlQueryNoConfiguredEntities,
487    SqlQueryEntityNotFound,
488    SqlDdlTargetRequired,
489    SqlDdlEntityNotConfigured,
490    SqlIntrospectionDisabled,
491    /// A complete accepted mutation omitted a required field.
492    MutationRequiredFieldMissing,
493    /// A logical write would move an accepted managed timestamp backward.
494    MutationManagedTimestampRegression,
495    /// A persisted row's stamp falls outside the accepted layout window.
496    PersistedRowLayoutOutsideAcceptedWindow,
497    /// A persisted row's physical slot count disagrees with its layout stamp.
498    PersistedRowSlotCountMismatch,
499    /// A generated field would collide with an accepted DDL-owned slot.
500    GeneratedFieldAfterDdlField,
501    /// A journaled mutation cannot reserve a representable post-commit revision.
502    JournalMutationRevisionExhausted,
503    /// A final canonical after-image violates one accepted row constraint or gate.
504    ConstraintViolation,
505    /// Accepted row-constraint metadata or its compiled program is inconsistent.
506    AcceptedRowConstraintProgramCorrupt,
507    /// A write conflicts with one incomplete accepted constraint activation.
508    ConstraintActivationWriteBlocked,
509    /// A live generated constraint activation no longer matches its proposal.
510    GeneratedConstraintActivationStale,
511    /// A caller explicitly authored a field owned by accepted database policy.
512    MutationDatabaseOwnedFieldExplicit,
513    /// A mixed structural mutation batch contained no operations.
514    MutationBatchEmpty,
515    /// A mixed structural mutation batch exceeded its operation-count bound.
516    MutationBatchTooManyItems,
517    /// A mixed structural mutation batch exceeded its staged-byte bound.
518    MutationBatchStagedBytesExceeded,
519    /// A mixed structural mutation result exceeded its encoded response bound.
520    MutationBatchResultBytesExceeded,
521    /// A mixed structural mutation batch crossed an accepted store boundary.
522    MutationBatchStoreMismatch,
523    /// A mixed structural mutation batch exceeded its distinct-entity bound.
524    MutationBatchTooManyEntities,
525    /// More than one mixed structural operation targeted the same accepted key.
526    MutationBatchDuplicateKey,
527    /// An operational report or reset endpoint requires a controller caller.
528    OperationalSurfaceControllerRequired,
529    /// An exact-key batch exceeded its admitted input item count.
530    ExactKeyBatchTooManyItems,
531    /// An exact-key batch exceeded its admitted encoded input-key bytes.
532    ExactKeyBatchInputBytesExceeded,
533    /// An exact-key batch exceeded its admitted distinct stored-row bytes.
534    ExactKeyBatchStoredBytesExceeded,
535    /// An exact-key batch exceeded its admitted logical result bytes.
536    ExactKeyBatchResultBytesExceeded,
537    /// One charged execution resource exceeded its absolute safety ceiling.
538    ExecutionBudgetExceeded,
539    /// One indivisible scalar-page unit cannot fit in an otherwise empty page envelope.
540    PageUnitTooLarge,
541    /// Database access was attempted without an active request-execution scope.
542    RequestExecutionScopeRequired,
543    /// An explicit request root conflicts with the root already active for this request.
544    RequestExecutionRootMismatch,
545    /// A successful generated SQL query reply exceeds the deployed IC query-response limit.
546    SqlQueryReplyBytesExceeded,
547    QueryExplainOutputExceeded,
548    /// A derived logical explain access tree exceeds the diagnostic depth limit.
549    QueryExplainDepthExceeded,
550    /// Startup recovery remains incomplete and ordinary database work must retry later.
551    DatabaseStartupRecoveryPending,
552    /// An application guard denied access to the generated SQL read surface.
553    SqlSurfacePolicyDenied,
554    /// An application guard denied access to the generated accepted-schema surface.
555    SchemaSurfacePolicyDenied,
556    /// A structural mutation batch exceeded its canonical prepared-commit work bound.
557    MutationBatchCommitWorkExceeded,
558    /// Retained journal debt leaves insufficient bounded convergence capacity.
559    ConvergenceBacklogPressure,
560    /// Requested manager bucket pages differ from the existing memory layout.
561    MemoryBucketSizeMismatch,
562}
563
564impl fmt::Debug for RuntimeBoundaryCode {
565    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
566        fmt_compact_code(f, *self as u16)
567    }
568}
569
570///
571/// SqlFeatureCode
572///
573/// Compact SQL feature identifier used by unsupported-feature diagnostics.
574/// Variant order is wire-order significant for public error-code offsets.
575///
576
577#[repr(u16)]
578#[derive(Clone, Copy, Eq, Hash, PartialEq)]
579pub enum SqlFeatureCode {
580    AggregateFilterClause,
581    AlterStatementBeyondAlterTable,
582    AlterTableAddColumnDuplicateDefault,
583    AlterTableAddColumnModifiers,
584    AlterTableAddStatementBeyondAddColumn,
585    AlterTableAlterColumnDropUnsupportedAction,
586    AlterTableAlterColumnModifiers,
587    AlterTableAlterColumnSetUnsupportedAction,
588    AlterTableAlterColumnUnsupportedAction,
589    AlterTableAlterStatementBeyondAlterColumn,
590    AlterTableDropColumnIfExistsSyntax,
591    AlterTableDropColumnModifiers,
592    AlterTableDropStatementBeyondDropColumn,
593    AlterTableRenameColumnMissingTo,
594    AlterTableRenameColumnModifiers,
595    AlterTableRenameStatementBeyondRenameColumn,
596    AlterTableUnsupportedOperation,
597    ColumnAlias,
598    CreateIndexIfNotExistsSyntax,
599    CreateIndexKeyOrderingModifiers,
600    CreateIndexModifiers,
601    CreateStatementBeyondCreateIndex,
602    DescribeModifier,
603    DdlSchemaVersionDuplicateExpectedClause,
604    DdlSchemaVersionDuplicateSetClause,
605    DropIndexModifiers,
606    DropIndexIfExistsSyntax,
607    DropStatementBeyondDropIndex,
608    ExpressionIndexUnsupportedFunction,
609    Having,
610    Insert,
611    Join,
612    LikePatternBeyondTrailingPrefix,
613    LowerFieldPredicateUnsupported,
614    MultiStatementSql,
615    NestedAggregateInput,
616    NestedProjectionFunctionInArithmetic,
617    OrderByUnsupportedForm,
618    Other,
619    PredicateStartsWithFirstArgument,
620    QuotedIdentifiers,
621    ReturningUnsupportedShape,
622    ScalarFunctionExpressionPosition,
623    ScaleTakingNumericFunctionExpressionPosition,
624    ShowColumnsModifiers,
625    ShowEntitiesModifiers,
626    ShowIndexesModifiers,
627    ShowMemoryModifiers,
628    ShowStoresModifiers,
629    ShowUnsupportedCommand,
630    SimpleCaseExpression,
631    StandaloneLiteralProjectionItem,
632    UnionIntersectExcept,
633    UnsupportedFunctionNamespace,
634    Update,
635    UpperFieldPredicateUnsupported,
636    WindowFunction,
637    With,
638    NumericScaleFunctionArguments,
639    OrderByFieldNotOrderable,
640    ShowConstraintsModifiers,
641    AlterTableAddConstraintBeyondCheck,
642    AlterTableAddConstraintModifiers,
643    AlterTableDropConstraintIfExistsSyntax,
644    AlterTableDropConstraintModifiers,
645    AlterTableValidateBeyondConstraint,
646    AlterTableValidateConstraintModifiers,
647    ShowRelationsModifiers,
648}
649
650impl fmt::Debug for SqlFeatureCode {
651    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
652        fmt_compact_code(f, *self as u16)
653    }
654}
655
656///
657/// SqlLoweringCode
658///
659/// Compact SQL lowering rejection identifier used after parsing succeeds but
660/// before a statement becomes canonical query intent.
661/// Variant order is wire-order significant for public error-code offsets.
662///
663
664#[repr(u16)]
665#[derive(Clone, Copy, Eq, Hash, PartialEq)]
666pub enum SqlLoweringCode {
667    EntityMismatch,
668    SelectProjectionShape,
669    SelectDistinct,
670    DistinctOrderByProjection,
671    GlobalAggregateProjection,
672    GlobalAggregateGroupBy,
673    SelectGroupByShape,
674    GroupedProjectionExplicitListRequired,
675    GroupedProjectionAggregateRequired,
676    GroupedProjectionNonGroupField,
677    GroupedProjectionScalarAfterAggregate,
678    HavingRequiresGroupBy,
679    SelectHavingShape,
680    AggregateInputExpressions,
681    WhereExpressionShape,
682    ParameterPlacement,
683    SqlDdlExecutionUnsupported,
684    BindingCount,
685    BindingFamily,
686    BindingLimit,
687}
688
689impl fmt::Debug for SqlLoweringCode {
690    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
691        fmt_compact_code(f, *self as u16)
692    }
693}
694
695///
696/// SqlSurfaceMismatchCode
697///
698/// Compact SQL endpoint surface mismatch identifier.
699/// Variant order is wire-order significant for public error-code offsets.
700///
701
702#[repr(u16)]
703#[derive(Clone, Copy, Eq, Hash, PartialEq)]
704pub enum SqlSurfaceMismatchCode {
705    QueryRejectsInsert,
706    QueryRejectsUpdate,
707    QueryRejectsDelete,
708    MutationRejectsSelect,
709    MutationRejectsExplain,
710    MutationRejectsDescribe,
711    MutationRejectsShowIndexes,
712    MutationRejectsShowColumns,
713    MutationRejectsShowEntities,
714    MutationRejectsShowStores,
715    MutationRejectsShowMemory,
716    MutationRequiresExplicitUpdateIntent,
717    MutationRejectsShowConstraints,
718    MutationRejectsShowRelations,
719}
720
721impl fmt::Debug for SqlSurfaceMismatchCode {
722    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
723        fmt_compact_code(f, *self as u16)
724    }
725}
726
727///
728/// SqlWriteBoundaryCode
729///
730/// Compact SQL write fail-closed boundary identifier.
731/// Variant order is wire-order significant for public error-code offsets.
732///
733
734#[repr(u16)]
735#[derive(Clone, Copy, Eq, Hash, PartialEq)]
736pub enum SqlWriteBoundaryCode {
737    PrimaryKeyLiteralIncompatible,
738    MissingPrimaryKey,
739    MissingRequiredFields,
740    ExplicitManagedField,
741    ExplicitGeneratedField,
742    InsertSelectRequiresScalar,
743    InsertSelectAggregateProjection,
744    InsertSelectWidthMismatch,
745    UpdatePrimaryKeyMutation,
746    InvalidFieldLiteral,
747    UnknownReturningField,
748    DuplicateReturningField,
749    UpdateMissingWherePredicate,
750    WriteOrderByUnsupportedShape,
751    ReturningResponseTooLarge,
752    ReturningRowsTooMany,
753    StagedRowsTooMany,
754    InsertDefaultRequiredField,
755    UpdateDefaultRequiredField,
756    UpdateDefaultDatabaseOwnedField,
757    ExactUpdateAssertionRequired,
758    ExactUpdateAssertionTooHigh,
759    ExactUpdateAffectedRowsExceeded,
760    ExactUpdateWindowUnsupported,
761    ExactUpdateScanBudgetExceeded,
762    ResumableUpdateWindowUnsupported,
763    ResumableUpdateReturningUnsupported,
764    ResumableUpdateRequiresJournaledStore,
765    ResumableUpdateAssignedFieldHasGlobalConstraint,
766    ResumableUpdateScopeDependsOnAssignedField,
767    ResumableUpdateScopeDependencyUnknown,
768    ResumableUpdateContinuationMalformed,
769    ResumableUpdateContinuationTargetMismatch,
770    ResumableUpdateContinuationSchemaMismatch,
771    ResumableUpdateContinuationScopeMismatch,
772    ResumableUpdateContinuationPatchMismatch,
773    ResumableUpdateContinuationBatchPolicyMismatch,
774    ResumableUpdateManagedFieldHasGlobalConstraint,
775}
776
777impl fmt::Debug for SqlWriteBoundaryCode {
778    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
779        fmt_compact_code(f, *self as u16)
780    }
781}
782
783///
784/// SchemaDdlAdmissionCode
785///
786/// Compact SQL DDL admission rejection reason.
787/// Variant order is wire-order significant for public error-code offsets.
788///
789
790#[repr(u16)]
791#[derive(Clone, Copy, Eq, Hash, PartialEq)]
792pub enum SchemaDdlAdmissionCode {
793    MissingExpectedSchemaVersion,
794    MissingNextSchemaVersion,
795    StaleExpectedSchemaVersion,
796    InvalidExpectedSchemaVersion,
797    InvalidNextSchemaVersion,
798    AcceptedSchemaChangeWithoutVersionBump,
799    EmptyVersionBump,
800    VersionGap,
801    VersionRollback,
802    FingerprintMethodMismatch,
803    UnsupportedTransitionClass,
804    PhysicalRunnerMissing,
805    ValidationFailed,
806    PublicationRaceLost,
807    InvalidAddColumnDefault,
808    InvalidAlterColumnDefault,
809    GeneratedIndexDropRejected,
810    SchemaRewriteRequiresMigration,
811    SchemaTransitionBudgetExceeded,
812    GeneratedFieldDefaultChangeRejected,
813    GeneratedFieldNullabilityChangeRejected,
814    RowLayoutVersionExhausted,
815}
816
817impl fmt::Debug for SchemaDdlAdmissionCode {
818    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
819        fmt_compact_code(f, *self as u16)
820    }
821}
822
823/// Machine-readable source-migration rejection or lifecycle finding.
824#[repr(u16)]
825#[derive(Clone, Copy, Eq, Hash, PartialEq)]
826pub enum SchemaMigrationCode {
827    Unadopted,
828    MissingMigration,
829    VersionGap,
830    Downgrade,
831    EmptyEntityVersionBump,
832    StaleAcceptedHead,
833    PlanChanged,
834    UnknownFromObject,
835    UnknownToObject,
836    KindMismatch,
837    IdentityConflict,
838    UnexplainedSchemaDifference,
839    UnsupportedTransform,
840    PhysicalRunnerMissing,
841    MigrationInProgress,
842    AbortTooLate,
843    ProgressCorrupt,
844    CandidateMismatch,
845    PublicationRaceLost,
846}
847
848impl SchemaMigrationCode {
849    /// Return the broad diagnostic category for this migration result.
850    #[must_use]
851    pub const fn diagnostic_code(self) -> DiagnosticCode {
852        match self {
853            Self::StaleAcceptedHead
854            | Self::PlanChanged
855            | Self::IdentityConflict
856            | Self::MigrationInProgress
857            | Self::AbortTooLate
858            | Self::PublicationRaceLost => DiagnosticCode::RuntimeConflict,
859            Self::ProgressCorrupt | Self::CandidateMismatch => DiagnosticCode::RuntimeCorruption,
860            Self::Unadopted
861            | Self::MissingMigration
862            | Self::VersionGap
863            | Self::Downgrade
864            | Self::EmptyEntityVersionBump
865            | Self::UnknownFromObject
866            | Self::UnknownToObject
867            | Self::KindMismatch
868            | Self::UnexplainedSchemaDifference
869            | Self::UnsupportedTransform
870            | Self::PhysicalRunnerMissing => DiagnosticCode::RuntimeUnsupported,
871        }
872    }
873}
874
875impl fmt::Debug for SchemaMigrationCode {
876    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
877        fmt_compact_code(f, *self as u16)
878    }
879}
880
881///
882/// DiagnosticDetail
883///
884/// Small structured diagnostic payload for callers and CLI rendering.
885///
886
887#[remain::sorted]
888#[derive(Clone, Copy, Eq, PartialEq)]
889pub enum DiagnosticDetail {
890    QueryKind { kind: QueryErrorKind },
891    QueryProjection { reason: QueryProjectionCode },
892    QueryReadAdmission { reason: QueryReadAdmissionCode },
893    RuntimeBoundary { boundary: RuntimeBoundaryCode },
894    RuntimeKind { kind: RuntimeErrorKind },
895    SchemaDdlAdmission { reason: SchemaDdlAdmissionCode },
896    SchemaMigration { reason: SchemaMigrationCode },
897    SqlLowering { reason: SqlLoweringCode },
898    SqlSurfaceMismatch { mismatch: SqlSurfaceMismatchCode },
899    SqlWriteBoundary { boundary: SqlWriteBoundaryCode },
900    UnsupportedSqlFeature { feature: SqlFeatureCode },
901}
902
903impl fmt::Debug for DiagnosticDetail {
904    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
905        fmt_compact_code(
906            f,
907            ErrorCode::from_parts(self.diagnostic_code(), Some(*self)).raw(),
908        )
909    }
910}
911
912///
913/// Diagnostic
914///
915/// Compact public diagnostic payload.
916///
917
918#[derive(Clone, Eq, PartialEq)]
919pub struct Diagnostic {
920    code: DiagnosticCode,
921    origin: ErrorOrigin,
922    detail: Option<DiagnosticDetail>,
923}
924
925impl Diagnostic {
926    /// Build a compact diagnostic from a code and optional structured detail.
927    #[must_use]
928    pub const fn new(
929        code: DiagnosticCode,
930        origin: ErrorOrigin,
931        detail: Option<DiagnosticDetail>,
932    ) -> Self {
933        Self {
934            code,
935            origin,
936            detail,
937        }
938    }
939
940    /// Build a compact diagnostic using the code's default origin.
941    #[must_use]
942    pub const fn from_code(code: DiagnosticCode) -> Self {
943        Self::new(code, code.origin(), None)
944    }
945
946    /// Return the stable diagnostic code.
947    #[must_use]
948    pub const fn code(&self) -> DiagnosticCode {
949        self.code
950    }
951
952    /// Return the diagnostic class.
953    #[must_use]
954    pub const fn class(&self) -> ErrorClass {
955        self.code.class()
956    }
957
958    /// Return the subsystem origin.
959    #[must_use]
960    pub const fn origin(&self) -> ErrorOrigin {
961        self.origin
962    }
963
964    /// Return structured diagnostic detail, when available.
965    #[must_use]
966    pub const fn detail(&self) -> Option<&DiagnosticDetail> {
967        self.detail.as_ref()
968    }
969
970    /// Return the numeric public wire code for this diagnostic.
971    #[must_use]
972    pub const fn error_code(&self) -> ErrorCode {
973        ErrorCode::from_parts(self.code, self.detail)
974    }
975}
976
977impl fmt::Debug for Diagnostic {
978    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
979        write!(f, "{}@{}", self.error_code().raw(), self.origin.wire_code())
980    }
981}
982
983fn fmt_compact_code(f: &mut fmt::Formatter<'_>, raw: u16) -> fmt::Result {
984    write!(f, "{raw}")
985}
986
987#[cfg(test)]
988mod tests {
989    use super::{
990        Diagnostic, DiagnosticCode, DiagnosticDetail, ErrorClass, ErrorCode, ErrorOrigin,
991        QueryProjectionCode, QueryReadAdmissionCode, SqlFeatureCode, SqlLoweringCode,
992        SqlWriteBoundaryCode,
993        registry::{DETAIL_ERROR_CODES, ORDERED_ERROR_CODES},
994    };
995
996    #[test]
997    fn diagnostic_from_code_uses_default_origin() {
998        let diagnostic = Diagnostic::from_code(DiagnosticCode::QueryPlan);
999
1000        assert_eq!(diagnostic.code(), DiagnosticCode::QueryPlan);
1001        assert_eq!(diagnostic.origin(), ErrorOrigin::Query);
1002    }
1003
1004    #[test]
1005    fn diagnostic_code_reports_broad_class() {
1006        assert_eq!(
1007            DiagnosticCode::QueryUnsupportedSqlFeature.class(),
1008            ErrorClass::Unsupported
1009        );
1010        assert_eq!(
1011            DiagnosticCode::QuerySqlSurfaceMismatch.class(),
1012            ErrorClass::Unsupported
1013        );
1014        assert_eq!(DiagnosticCode::QueryPlan.class(), ErrorClass::Query);
1015        assert_eq!(
1016            DiagnosticCode::StoreCorruption.class(),
1017            ErrorClass::Corruption
1018        );
1019    }
1020
1021    #[test]
1022    fn class_and_origin_wire_codes_round_trip() {
1023        for (class, raw) in [
1024            (ErrorClass::Query, 1),
1025            (ErrorClass::Corruption, 2),
1026            (ErrorClass::IncompatiblePersistedFormat, 3),
1027            (ErrorClass::NotFound, 4),
1028            (ErrorClass::Internal, 5),
1029            (ErrorClass::Conflict, 6),
1030            (ErrorClass::Unsupported, 7),
1031            (ErrorClass::InvariantViolation, 8),
1032        ] {
1033            assert_eq!(class.wire_code(), raw);
1034            assert_eq!(ErrorClass::from_wire_code(raw), Some(class));
1035            assert_eq!(format!("{class:?}"), raw.to_string());
1036        }
1037
1038        for (origin, raw) in [
1039            (ErrorOrigin::Cursor, 1),
1040            (ErrorOrigin::Executor, 2),
1041            (ErrorOrigin::Identity, 3),
1042            (ErrorOrigin::Index, 4),
1043            (ErrorOrigin::Interface, 5),
1044            (ErrorOrigin::Planner, 6),
1045            (ErrorOrigin::Query, 7),
1046            (ErrorOrigin::Recovery, 8),
1047            (ErrorOrigin::Response, 9),
1048            (ErrorOrigin::Runtime, 10),
1049            (ErrorOrigin::Serialize, 11),
1050            (ErrorOrigin::Store, 12),
1051        ] {
1052            assert_eq!(origin.wire_code(), raw);
1053            assert_eq!(ErrorOrigin::from_known_wire_code(raw), Some(origin));
1054            assert_eq!(ErrorOrigin::from_wire_code(raw), origin);
1055            assert_eq!(format!("{origin:?}"), raw.to_string());
1056        }
1057
1058        assert_eq!(ErrorClass::from_wire_code(0), None);
1059        assert_eq!(ErrorOrigin::from_known_wire_code(0), None);
1060        assert_eq!(ErrorOrigin::from_wire_code(0), ErrorOrigin::Runtime);
1061    }
1062
1063    #[test]
1064    fn public_error_codes_are_sequential() {
1065        let first = ORDERED_ERROR_CODES
1066            .first()
1067            .expect("public error-code registry is non-empty")
1068            .raw();
1069
1070        assert_eq!(first, 1);
1071
1072        for (index, code) in ORDERED_ERROR_CODES.iter().enumerate() {
1073            let expected = first + u16::try_from(index).expect("test error-code index fits u16");
1074            assert_eq!(code.raw(), expected);
1075            assert_eq!(ErrorCode::known(code.raw()), Some(*code));
1076            assert!(code.is_known());
1077        }
1078
1079        let last = ORDERED_ERROR_CODES
1080            .last()
1081            .expect("public error-code registry is non-empty")
1082            .raw();
1083
1084        assert_eq!(last, 275);
1085    }
1086
1087    #[test]
1088    fn all_public_error_codes_round_trip_through_diagnostic_parts() {
1089        let first = ORDERED_ERROR_CODES
1090            .first()
1091            .expect("public error-code registry is non-empty")
1092            .raw();
1093        let last = ORDERED_ERROR_CODES
1094            .last()
1095            .expect("public error-code registry is non-empty")
1096            .raw();
1097
1098        for raw in first..=last {
1099            let code = ErrorCode::from_raw(raw);
1100            let diagnostic_code = code.diagnostic_code();
1101            let diagnostic_detail = code.diagnostic_detail();
1102            let rebuilt = ErrorCode::from_parts(diagnostic_code, diagnostic_detail);
1103
1104            assert_eq!(rebuilt.raw(), raw);
1105
1106            let diagnostic = code.diagnostic(ErrorOrigin::Runtime);
1107
1108            assert_eq!(diagnostic.code(), diagnostic_code);
1109            assert_eq!(diagnostic.detail(), diagnostic_detail.as_ref());
1110            assert_eq!(diagnostic.error_code().raw(), raw);
1111        }
1112    }
1113
1114    #[test]
1115    fn invalid_raw_error_codes_fail_closed_to_runtime_internal() {
1116        let first_unknown = ORDERED_ERROR_CODES
1117            .last()
1118            .expect("public error-code registry is non-empty")
1119            .raw()
1120            .checked_add(1)
1121            .expect("public error-code registry retains an unknown successor");
1122
1123        for raw in [0, first_unknown, u16::MAX] {
1124            let code = ErrorCode::from_raw(raw);
1125
1126            assert_eq!(ErrorCode::known(raw), None);
1127            assert!(!code.is_known());
1128            assert_eq!(code.diagnostic_code(), DiagnosticCode::RuntimeInternal);
1129            assert_eq!(code.diagnostic_detail(), None);
1130            assert_eq!(code.class(), ErrorClass::Internal);
1131
1132            let diagnostic = code.diagnostic(ErrorOrigin::Query);
1133
1134            assert_eq!(diagnostic.code(), DiagnosticCode::RuntimeInternal);
1135            assert_eq!(diagnostic.origin(), ErrorOrigin::Query);
1136            assert_eq!(diagnostic.detail(), None);
1137            assert_eq!(diagnostic.error_code(), ErrorCode::RUNTIME_INTERNAL);
1138        }
1139    }
1140
1141    #[test]
1142    fn from_parts_requires_detail_to_match_broad_code() {
1143        let detail = Some(DiagnosticDetail::UnsupportedSqlFeature {
1144            feature: SqlFeatureCode::Join,
1145        });
1146
1147        assert_eq!(
1148            ErrorCode::from_parts(DiagnosticCode::QueryUnsupportedSqlFeature, detail),
1149            ErrorCode::SQL_FEATURE_JOIN
1150        );
1151        assert_eq!(
1152            ErrorCode::from_parts(DiagnosticCode::QueryPlan, detail),
1153            ErrorCode::QUERY_PLAN
1154        );
1155    }
1156
1157    #[test]
1158    fn detail_bearing_registry_entries_round_trip_directly() {
1159        assert!(!DETAIL_ERROR_CODES.is_empty());
1160
1161        for &(code, diagnostic_code, detail) in DETAIL_ERROR_CODES {
1162            assert_eq!(ErrorCode::from_parts(diagnostic_code, Some(detail)), code);
1163            assert_eq!(code.diagnostic_code(), diagnostic_code);
1164            assert_eq!(code.diagnostic_detail(), Some(detail));
1165            assert_eq!(detail.diagnostic_code(), diagnostic_code);
1166        }
1167    }
1168
1169    #[test]
1170    fn diagnostic_detail_reports_generated_broad_code() {
1171        let detail = DiagnosticDetail::UnsupportedSqlFeature {
1172            feature: SqlFeatureCode::Join,
1173        };
1174
1175        assert_eq!(
1176            detail.diagnostic_code(),
1177            DiagnosticCode::QueryUnsupportedSqlFeature
1178        );
1179        assert_eq!(format!("{detail:?}"), "61");
1180    }
1181
1182    #[test]
1183    fn public_error_codes_reconstruct_shifted_details() {
1184        assert_eq!(
1185            ErrorCode::QUERY_UNKNOWN_AGGREGATE_TARGET_FIELD.diagnostic_code(),
1186            DiagnosticCode::QueryUnknownAggregateTargetField
1187        );
1188        assert_eq!(
1189            ErrorCode::SQL_FEATURE_JOIN.diagnostic_detail(),
1190            Some(DiagnosticDetail::UnsupportedSqlFeature {
1191                feature: SqlFeatureCode::Join,
1192            })
1193        );
1194        assert_eq!(
1195            ErrorCode::QUERY_PROJECTION_NUMERIC_LITERAL_REQUIRED.diagnostic_detail(),
1196            Some(DiagnosticDetail::QueryProjection {
1197                reason: QueryProjectionCode::NumericLiteralRequired,
1198            })
1199        );
1200        assert_eq!(
1201            ErrorCode::QUERY_READ_PUBLIC_REQUIRES_LIMIT.diagnostic_detail(),
1202            Some(DiagnosticDetail::QueryReadAdmission {
1203                reason: QueryReadAdmissionCode::PublicQueryRequiresLimit,
1204            })
1205        );
1206        assert_eq!(
1207            ErrorCode::SQL_LOWERING_DISTINCT_ORDER_BY_PROJECTION.diagnostic_detail(),
1208            Some(DiagnosticDetail::SqlLowering {
1209                reason: SqlLoweringCode::DistinctOrderByProjection,
1210            })
1211        );
1212        assert_eq!(
1213            ErrorCode::SQL_WRITE_RETURNING_RESPONSE_TOO_LARGE.diagnostic_detail(),
1214            Some(DiagnosticDetail::SqlWriteBoundary {
1215                boundary: SqlWriteBoundaryCode::ReturningResponseTooLarge,
1216            })
1217        );
1218        assert_eq!(
1219            ErrorCode::SQL_WRITE_RETURNING_ROWS_TOO_MANY.diagnostic_detail(),
1220            Some(DiagnosticDetail::SqlWriteBoundary {
1221                boundary: SqlWriteBoundaryCode::ReturningRowsTooMany,
1222            })
1223        );
1224    }
1225}