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}