Skip to main content

algeon_datafusion/error/
code.rs

1use std::fmt;
2
3use query_planning::error::QueryErrorCode;
4
5/// Declares [`ErrorCode`] from one list: each variant with its report label,
6/// [`ErrorKind`], and [`ErrorStatus`]. Every table derives from the list, so a
7/// new code cannot ship without all three.
8macro_rules! error_codes {
9    ($($(#[$doc:meta])* $code:ident => $label:literal, $kind:ident, $status:ident;)+) => {
10        /// Stable, machine-readable adapter error codes.
11        ///
12        /// Codes describe the closest adapter-level cause. [`ErrorKind`] and
13        /// [`ErrorStatus`] are derived from the code by [`ErrorCode::kind`]
14        /// and [`ErrorCode::status`]. Consumers should treat those methods as
15        /// the policy contract and should not infer behavior from variant
16        /// names.
17        #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
18        #[non_exhaustive]
19        pub enum ErrorCode {
20            $($(#[$doc])* $code,)+
21        }
22
23        impl ErrorCode {
24            /// Every adapter error code, in declaration order.
25            pub const ALL: &'static [Self] = &[$(Self::$code),+];
26
27            /// Stable snake-case code label used in reports and error text.
28            pub const fn as_str(self) -> &'static str {
29                match self {
30                    $(Self::$code => $label,)+
31                }
32            }
33
34            /// Stable coarse category of this code.
35            ///
36            /// `kind` is the broad bucket used for diagnostics and
37            /// aggregation. It is less specific than [`ErrorCode`] and stays
38            /// stable for consumers that group failures without depending on
39            /// every code variant.
40            pub const fn kind(self) -> ErrorKind {
41                match self {
42                    $(Self::$code => ErrorKind::$kind,)+
43                }
44            }
45
46            /// Retry/remediation status of this code.
47            ///
48            /// Attempt retry advice is derived only after terminal evidence is
49            /// sealed. [`ErrorStatus::Persistent`] means a repeated or
50            /// known-continuing dependency failure that requires escalation or
51            /// intervention, while [`ErrorStatus::Permanent`] means the
52            /// request, input, configuration, or code must change before the
53            /// same operation can succeed.
54            pub const fn status(self) -> ErrorStatus {
55                match self {
56                    $(Self::$code => ErrorStatus::$status,)+
57                }
58            }
59        }
60    };
61}
62
63error_codes! {
64    /// Query execution was canceled by the caller.
65    Cancelled => "cancelled", Cancelled, Permanent;
66    /// SQL text could not be parsed.
67    SqlParse => "sql_parse", InvalidInput, Permanent;
68    /// The server request envelope or handle was invalid for the operation.
69    InvalidRequest => "invalid_request", InvalidInput, Permanent;
70    /// A server, backend, or device configuration value is invalid or
71    /// inconsistent with the discovered hardware.
72    ///
73    /// The same configuration fails again until it changes; the message names
74    /// the setting, its value, and the largest accepted value when one exists.
75    InvalidConfiguration => "invalid_configuration", InvalidInput, Permanent;
76    /// DataFusion rejected the query during logical or physical planning.
77    DataFusionPlanning => "datafusion_planning", Planning, Permanent;
78    /// Adapter-owned planning exceeded its configured time budget.
79    ///
80    /// This is classified as permanent for the same request and budget: retry
81    /// with table maintenance, a narrower request, or a larger configured
82    /// planning budget.
83    PlanningTimeout => "planning_timeout", Planning, Permanent;
84    /// A requested catalog, schema, table, table function, or similar named object was not found.
85    ObjectNotFound => "object_not_found", NotFound, Permanent;
86    /// Source object storage denied access to the requested object or metadata.
87    SourceAccessDenied => "source_access_denied", PermissionDenied, Permanent;
88    /// Source object storage lacked valid authentication credentials.
89    SourceUnauthenticated => "source_unauthenticated", Unauthenticated, Permanent;
90    /// Source object storage could not find the requested object.
91    SourceObjectNotFound => "source_object_not_found", NotFound, Permanent;
92    /// Source object storage failed with a retryable transport or service error.
93    SourceTemporaryFailure => "source_temporary_failure", DependencyFailure, Temporary;
94    /// A source reader or source dependency failed permanently while serving the request.
95    SourceDependencyFailure => "source_dependency_failure", DependencyFailure, Permanent;
96    /// A named field was not present in a resolved schema.
97    FieldNotFound => "field_not_found", InvalidInput, Permanent;
98    /// Materialized graph edges or side inputs violate the graph operation's
99    /// declared input contract.
100    GraphInputInvalid => "graph_input_invalid", InvalidInput, Permanent;
101    /// A completed-plan requirement needed a GPU operation that the exact
102    /// final physical plan could not provide.
103    RequiredGpuOperationUnsupported => "required_gpu_operation_unsupported", Unsupported, Permanent;
104    /// The exact completed physical plan did not satisfy a configured final
105    /// plan requirement.
106    FinalPlanRequirementUnsatisfied => "final_plan_requirement_unsatisfied", Planning, Permanent;
107    /// A physical operator is unsupported by the GPU/native adapter path.
108    UnsupportedOperator => "unsupported_operator", Unsupported, Permanent;
109    /// A physical expression or expression form is unsupported.
110    UnsupportedExpression => "unsupported_expression", Unsupported, Permanent;
111    /// A data type, physical representation, or type combination is unsupported.
112    UnsupportedType => "unsupported_type", Unsupported, Permanent;
113    /// A DataFusion physical plan is invalid for the adapter boundary.
114    ///
115    /// Use this only for malformed or inconsistent plans. A well-formed plan
116    /// outside the supported capability catalogue is reported as unsupported.
117    InvalidExecutionPlan => "invalid_execution_plan", Planning, Permanent;
118    /// An execution plan had an impossible child count for the adapter node.
119    InvalidChildCount => "invalid_child_count", Internal, Permanent;
120    /// cuDF rejected an input, including permanent `NotFound` conditions.
121    CudfInvalidInput => "cudf_invalid_input", InvalidInput, Permanent;
122    /// cuDF reported a permanent unsupported capability.
123    CudfUnsupported => "cudf_unsupported", Unsupported, Permanent;
124    /// A host materialization size computation overflowed the platform range.
125    SizeOverflow => "size_overflow", ResourceExhausted, Permanent;
126    /// One unsplittable Arrow host row exceeds the configured deterministic
127    /// materialization ceiling.
128    HostMaterializationLimitExceeded => "host_materialization_limit_exceeded", ResourceExhausted, Permanent;
129    /// A bounded host-staging boundary exceeded its configured cumulative byte
130    /// ceiling before GPU admission or a device-to-host copy.
131    HostStagingLimitExceeded => "host_staging_limit_exceeded", ResourceExhausted, Permanent;
132    /// DataFusion's host memory pool rejected a reservation.
133    ///
134    /// Represents a temporary resource exhaustion in the host-side pool.
135    /// Device memory is governed by the query domain allocator and reports
136    /// through [`Self::QueryMemoryLimitExceeded`], [`Self::DeviceOutOfMemory`],
137    /// and [`Self::DeviceAllocationFailed`].
138    HostMemoryExhausted => "host_memory_exhausted", ResourceExhausted, Temporary;
139    /// cuDF reported a temporary dependency/runtime failure.
140    CudfTemporaryFailure => "cudf_temporary_failure", DependencyFailure, Temporary;
141    /// cuDF reported a persistent dependency/runtime failure.
142    ///
143    /// The failure has been observed to recur or is known to continue. Stop
144    /// automatic retries and escalate or wait for intervention.
145    CudfPersistentFailure => "cudf_persistent_failure", DependencyFailure, Persistent;
146    /// cuDF reported a permanent dependency failure.
147    CudfDependencyFailure => "cudf_dependency_failure", DependencyFailure, Permanent;
148    /// An iterative native algorithm reached its configured iteration bound.
149    AlgorithmDidNotConverge => "algorithm_did_not_converge", DependencyFailure, Permanent;
150    /// cuDF reported an invariant or execution-context violation.
151    CudfInvariantViolation => "cudf_invariant_violation", InvariantViolation, Permanent;
152    /// The native admission controller could not reserve requested memory.
153    ///
154    /// Represents a temporary resource condition propagated from native
155    /// execution, separate from a cuDF allocator failure.
156    MemoryReservationFailed => "memory_reservation_failed", ResourceExhausted, Temporary;
157    /// The native query service rejected work because admission capacity was exhausted.
158    ServiceOverloaded => "service_overloaded", ResourceExhausted, Temporary;
159    /// The requested query cap cannot fit on any eligible device after
160    /// subtracting resident ownership.
161    QueryBudgetUnavailable => "query_budget_unavailable", ResourceExhausted, Temporary;
162    /// The query domain rejected an actual or exact-required allocation over
163    /// the query's fixed memory cap.
164    QueryMemoryLimitExceeded => "query_memory_limit_exceeded", ResourceExhausted, Permanent;
165    /// A device allocation failed with confirmed physical-memory provenance.
166    DeviceOutOfMemory => "device_out_of_memory", ResourceExhausted, Temporary;
167    /// A device allocation failed without confirmed physical-memory provenance.
168    DeviceAllocationFailed => "device_allocation_failed", ResourceExhausted, Temporary;
169    /// A persistent device or CUDA runtime fault made the affected device
170    /// unavailable.
171    DeviceFault => "device_fault", DependencyFailure, Persistent;
172    /// Cleanup or synchronization could not establish safe release of the
173    /// attempt's device resources.
174    CleanupFailed => "cleanup_failed", DependencyFailure, Persistent;
175    /// An owner, provenance, ledger, or stream-routing invariant was violated.
176    InternalInvariantViolation => "internal_invariant_violation", InvariantViolation, Permanent;
177    /// One logical Arrow row cannot be represented within the configured
178    /// Flight/gRPC message limit.
179    ///
180    /// This is permanent for the same row and transport limit: callers must
181    /// project a smaller value or otherwise change the request. The server
182    /// must not raise tonic's message-size limit to hide this condition.
183    FlightFrameTooLarge => "flight_frame_too_large", ResourceExhausted, Permanent;
184    /// The Flight SQL server transport failed outside request handling.
185    ServerTransportFailure => "server_transport_failure", DependencyFailure, Permanent;
186    /// An internal adapter bug or unreachable condition.
187    Internal => "internal", Internal, Permanent;
188}
189
190impl fmt::Display for ErrorCode {
191    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
192        f.write_str(self.as_str())
193    }
194}
195
196/// Coarse adapter error category.
197#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
198#[non_exhaustive]
199pub enum ErrorKind {
200    /// Caller-requested cancellation stopped execution.
201    Cancelled,
202    /// Requested catalog, schema, table, table function, or similar named object was not found.
203    NotFound,
204    /// The caller or configured source credentials do not have permission.
205    PermissionDenied,
206    /// The caller or configured source credentials are not authenticated.
207    Unauthenticated,
208    /// Planning, validation, or physical-plan construction failure.
209    Planning,
210    /// Caller-provided input is invalid for the requested operation.
211    InvalidInput,
212    /// Requested operator, expression, type, or native shape is unsupported.
213    Unsupported,
214    /// Runtime execution failure at the adapter boundary.
215    Execution,
216    /// Memory or another bounded resource was exhausted or could not be reserved.
217    ResourceExhausted,
218    /// cuDF or another dependency failed outside resource-exhaustion handling.
219    DependencyFailure,
220    /// An adapter or cuDF invariant was violated.
221    InvariantViolation,
222    /// Internal bug or unreachable condition.
223    Internal,
224}
225
226impl ErrorKind {
227    /// Stable snake-case category label used in reports and error text.
228    pub const fn as_str(self) -> &'static str {
229        match self {
230            Self::Cancelled => "cancelled",
231            Self::NotFound => "not_found",
232            Self::PermissionDenied => "permission_denied",
233            Self::Unauthenticated => "unauthenticated",
234            Self::Planning => "planning",
235            Self::InvalidInput => "invalid_input",
236            Self::Unsupported => "unsupported",
237            Self::Execution => "execution",
238            Self::ResourceExhausted => "resource_exhausted",
239            Self::DependencyFailure => "dependency_failure",
240            Self::InvariantViolation => "invariant_violation",
241            Self::Internal => "internal",
242        }
243    }
244}
245
246impl fmt::Display for ErrorKind {
247    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
248        f.write_str(self.as_str())
249    }
250}
251
252/// Retry and remediation status for adapter errors.
253///
254/// Provides coarse source classification. Attempt retry advice is derived only
255/// after terminal evidence is sealed.
256#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
257pub enum ErrorStatus {
258    /// The same request will not succeed without changing its query, input,
259    /// configuration, or code. Do not retry it automatically.
260    Permanent,
261    /// The failure may clear without external repair. A sealed attempt report
262    /// still needs terminal evidence before it can advise backoff or retry.
263    Temporary,
264    /// A dependency/runtime failure has been observed to recur or is known to
265    /// continue. Stop automatic retries and escalate or wait for intervention.
266    ///
267    /// This remains distinct from [`Self::Permanent`] because it records a
268    /// continuing dependency failure, while `Permanent` records a request that
269    /// must change. Neither status alone is retry advice.
270    Persistent,
271}
272
273impl ErrorStatus {
274    /// Stable snake-case status label used in reports and error text.
275    pub const fn as_str(self) -> &'static str {
276        match self {
277            Self::Permanent => "permanent",
278            Self::Temporary => "temporary",
279            Self::Persistent => "persistent",
280        }
281    }
282}
283
284impl fmt::Display for ErrorStatus {
285    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
286        f.write_str(self.as_str())
287    }
288}
289
290/// Maps a native engine error code to the closest adapter code.
291///
292/// The mapping is lossy at the top-level code: native
293/// `UnsupportedPipelineContract` becomes adapter `UnsupportedOperator` because
294/// the adapter has no pipeline-contract code. The exact native code remains
295/// available in the `native_code` fact attached by `Error::from_native`.
296pub(crate) fn code_for_native_error(code: QueryErrorCode) -> ErrorCode {
297    match code {
298        QueryErrorCode::Cancelled => ErrorCode::Cancelled,
299        QueryErrorCode::UnsupportedPipelineContract => ErrorCode::UnsupportedOperator,
300        QueryErrorCode::UnsupportedOperator => ErrorCode::UnsupportedOperator,
301        QueryErrorCode::UnsupportedExpression => ErrorCode::UnsupportedExpression,
302        QueryErrorCode::UnsupportedType => ErrorCode::UnsupportedType,
303        QueryErrorCode::InvalidPlan => ErrorCode::InvalidExecutionPlan,
304        QueryErrorCode::InvalidAttemptCorrelation => ErrorCode::InvalidRequest,
305        QueryErrorCode::InvalidConfiguration => ErrorCode::InvalidConfiguration,
306        QueryErrorCode::GraphInputInvalid => ErrorCode::GraphInputInvalid,
307        QueryErrorCode::CudfInvalidInput => ErrorCode::CudfInvalidInput,
308        QueryErrorCode::CudfUnsupported => ErrorCode::CudfUnsupported,
309        QueryErrorCode::SizeOverflow => ErrorCode::SizeOverflow,
310        QueryErrorCode::HostMaterializationLimitExceeded => ErrorCode::HostMaterializationLimitExceeded,
311        QueryErrorCode::CudfTemporaryFailure => ErrorCode::CudfTemporaryFailure,
312        QueryErrorCode::CudfPersistentFailure => ErrorCode::CudfPersistentFailure,
313        QueryErrorCode::CudfDependencyFailure => ErrorCode::CudfDependencyFailure,
314        QueryErrorCode::AlgorithmDidNotConverge => ErrorCode::AlgorithmDidNotConverge,
315        QueryErrorCode::SourceAccessDenied => ErrorCode::SourceAccessDenied,
316        QueryErrorCode::SourceUnauthenticated => ErrorCode::SourceUnauthenticated,
317        QueryErrorCode::SourceObjectNotFound => ErrorCode::SourceObjectNotFound,
318        QueryErrorCode::SourceTemporaryFailure => ErrorCode::SourceTemporaryFailure,
319        QueryErrorCode::CudfInvariantViolation => ErrorCode::CudfInvariantViolation,
320        QueryErrorCode::MemoryReservationFailed => ErrorCode::MemoryReservationFailed,
321        QueryErrorCode::ServiceOverloaded => ErrorCode::ServiceOverloaded,
322        QueryErrorCode::QueryBudgetUnavailable => ErrorCode::QueryBudgetUnavailable,
323        QueryErrorCode::QueryMemoryLimitExceeded => ErrorCode::QueryMemoryLimitExceeded,
324        QueryErrorCode::DeviceOutOfMemory => ErrorCode::DeviceOutOfMemory,
325        QueryErrorCode::DeviceAllocationFailed => ErrorCode::DeviceAllocationFailed,
326        QueryErrorCode::DeviceFault => ErrorCode::DeviceFault,
327        QueryErrorCode::CleanupFailed => ErrorCode::CleanupFailed,
328        QueryErrorCode::InternalInvariantViolation => ErrorCode::InternalInvariantViolation,
329        QueryErrorCode::Internal => ErrorCode::Internal,
330    }
331}