1use std::any::Any;
16
17use arrow_schema::ArrowError;
18use common_error::ext::{ErrorExt, RetryHint, retry_hint_from_io_error};
19use common_error::status_code::StatusCode;
20use common_macro::stack_trace_debug;
21use datafusion::parquet::errors::ParquetError;
22use object_store::error::retry_hint_from_opendal_error;
23use snafu::{Location, Snafu};
24use url::ParseError;
25
26#[derive(Snafu)]
27#[snafu(visibility(pub))]
28#[stack_trace_debug]
29pub enum Error {
30 #[snafu(display("Parquet write cancelled"))]
31 ParquetWriteCancelled {},
32
33 #[snafu(display("Parquet writer limits must be positive"))]
34 InvalidParquetWriterLimits {},
35
36 #[snafu(display("Parquet writer resource limit exceeded: {reason}"))]
37 ParquetWriterResource { reason: String },
38
39 #[snafu(display("Unsupported compression type: {}", compression_type))]
40 UnsupportedCompressionType {
41 compression_type: String,
42 #[snafu(implicit)]
43 location: Location,
44 },
45
46 #[snafu(display("Unsupported backend protocol: {}, url: {}", protocol, url))]
47 UnsupportedBackendProtocol {
48 protocol: String,
49 #[snafu(implicit)]
50 location: Location,
51 url: String,
52 },
53
54 #[snafu(display("Unsupported format protocol: {}", format))]
55 UnsupportedFormat {
56 format: String,
57 #[snafu(implicit)]
58 location: Location,
59 },
60
61 #[snafu(display("empty host: {}", url))]
62 EmptyHostPath {
63 url: String,
64 #[snafu(implicit)]
65 location: Location,
66 },
67
68 #[snafu(display("Invalid url: {}", url))]
69 InvalidUrl {
70 url: String,
71 #[snafu(source)]
72 error: ParseError,
73 #[snafu(implicit)]
74 location: Location,
75 },
76
77 #[snafu(display(
78 "SQL access to the local filesystem is disabled for '{}'; use S3, OSS, GCS, or AzBlob instead",
79 path
80 ))]
81 LocalFileAccessDisabled {
82 path: String,
83 #[snafu(implicit)]
84 location: Location,
85 },
86
87 #[snafu(display(
88 "Local filesystem path '{}' is outside the configured copy root or is unsafe: {}; use a path relative to the copy root or use S3, OSS, GCS, or AzBlob",
89 path,
90 reason
91 ))]
92 LocalFileAccessDenied {
93 path: String,
94 reason: String,
95 #[snafu(implicit)]
96 location: Location,
97 },
98
99 #[snafu(display(
100 "Local filesystem path '{}' does not exist within the configured copy root",
101 path
102 ))]
103 LocalFilePathNotFound {
104 path: String,
105 #[snafu(implicit)]
106 location: Location,
107 },
108
109 #[snafu(display("Location must include a file or object name: '{}'", path))]
110 MissingObjectName {
111 path: String,
112 #[snafu(implicit)]
113 location: Location,
114 },
115
116 #[snafu(display("Invalid local filesystem root '{}'", root))]
117 InvalidLocalFileRoot {
118 root: String,
119 #[snafu(source)]
120 error: std::io::Error,
121 #[snafu(implicit)]
122 location: Location,
123 },
124
125 #[snafu(display("Invalid local filesystem root '{}': {}", root, reason))]
126 InvalidLocalFileRootConfig {
127 root: String,
128 reason: String,
129 #[snafu(implicit)]
130 location: Location,
131 },
132
133 #[snafu(display("Failed to build backend"))]
134 BuildBackend {
135 #[snafu(source)]
136 error: object_store::Error,
137 #[snafu(implicit)]
138 location: Location,
139 },
140
141 #[snafu(display("Failed to build orc reader"))]
142 OrcReader {
143 #[snafu(implicit)]
144 location: Location,
145 #[snafu(source)]
146 error: orc_rust::error::OrcError,
147 },
148
149 #[snafu(display("Failed to read object from path: {}", path))]
150 ReadObject {
151 path: String,
152 #[snafu(implicit)]
153 location: Location,
154 #[snafu(source)]
155 error: object_store::Error,
156 },
157
158 #[snafu(display("Failed to write object to path: {}", path))]
159 WriteObject {
160 path: String,
161 #[snafu(implicit)]
162 location: Location,
163 #[snafu(source)]
164 error: object_store::Error,
165 },
166
167 #[snafu(display("Failed to write"))]
168 AsyncWrite {
169 #[snafu(source)]
170 error: std::io::Error,
171 #[snafu(implicit)]
172 location: Location,
173 },
174
175 #[snafu(display("Failed to write record batch"))]
176 WriteRecordBatch {
177 #[snafu(implicit)]
178 location: Location,
179 #[snafu(source)]
180 error: ArrowError,
181 },
182
183 #[snafu(display("Failed to encode record batch"))]
184 EncodeRecordBatch {
185 #[snafu(implicit)]
186 location: Location,
187 #[snafu(source)]
188 error: ParquetError,
189 },
190
191 #[snafu(display("Failed to read record batch"))]
192 ReadRecordBatch {
193 #[snafu(implicit)]
194 location: Location,
195 #[snafu(source)]
196 error: datafusion::error::DataFusionError,
197 },
198
199 #[snafu(display("Failed to read parquet"))]
200 ReadParquetSnafu {
201 #[snafu(implicit)]
202 location: Location,
203 #[snafu(source)]
204 error: datafusion::parquet::errors::ParquetError,
205 },
206
207 #[snafu(display("Failed to convert parquet to schema"))]
208 ParquetToSchema {
209 #[snafu(implicit)]
210 location: Location,
211 #[snafu(source)]
212 error: datafusion::parquet::errors::ParquetError,
213 },
214
215 #[snafu(display("Failed to infer schema from file"))]
216 InferSchema {
217 #[snafu(implicit)]
218 location: Location,
219 #[snafu(source)]
220 error: arrow_schema::ArrowError,
221 },
222
223 #[snafu(display("Failed to list object in path: {}", path))]
224 ListObjects {
225 path: String,
226 #[snafu(implicit)]
227 location: Location,
228 #[snafu(source)]
229 error: object_store::Error,
230 },
231
232 #[snafu(display("Invalid connection: {}", msg))]
233 InvalidConnection {
234 msg: String,
235 #[snafu(implicit)]
236 location: Location,
237 },
238
239 #[snafu(display("Failed to join handle"))]
240 JoinHandle {
241 #[snafu(implicit)]
242 location: Location,
243 #[snafu(source)]
244 error: tokio::task::JoinError,
245 },
246
247 #[snafu(display("Failed to parse format {} with value: {}", key, value))]
248 ParseFormat {
249 key: String,
250 value: String,
251 #[snafu(implicit)]
252 location: Location,
253 },
254
255 #[snafu(display("Failed to merge schema"))]
256 MergeSchema {
257 #[snafu(source)]
258 error: arrow_schema::ArrowError,
259 #[snafu(implicit)]
260 location: Location,
261 },
262
263 #[snafu(display("Failed to write parquet file, path: {}", path))]
264 WriteParquet {
265 path: String,
266 #[snafu(implicit)]
267 location: Location,
268 #[snafu(source)]
269 error: parquet::errors::ParquetError,
270 },
271
272 #[snafu(transparent)]
273 DataFusion {
274 #[snafu(implicit)]
275 location: Location,
276 #[snafu(source)]
277 error: datafusion::error::DataFusionError,
278 },
279}
280
281pub type Result<T> = std::result::Result<T, Error>;
282
283impl ErrorExt for Error {
284 fn status_code(&self) -> StatusCode {
285 use Error::*;
286 match self {
287 ParquetWriteCancelled {} => StatusCode::Cancelled,
288 InvalidParquetWriterLimits {} => StatusCode::InvalidArguments,
289 ParquetWriterResource { .. } => StatusCode::Suspended,
290 BuildBackend { .. }
291 | ListObjects { .. }
292 | ReadObject { .. }
293 | WriteObject { .. }
294 | AsyncWrite { .. }
295 | WriteParquet { .. } => StatusCode::StorageUnavailable,
296
297 UnsupportedBackendProtocol { .. }
298 | UnsupportedCompressionType { .. }
299 | UnsupportedFormat { .. }
300 | InvalidConnection { .. }
301 | InvalidUrl { .. }
302 | LocalFileAccessDisabled { .. }
303 | LocalFileAccessDenied { .. }
304 | LocalFilePathNotFound { .. }
305 | MissingObjectName { .. }
306 | InvalidLocalFileRoot { .. }
307 | InvalidLocalFileRootConfig { .. }
308 | EmptyHostPath { .. }
309 | InferSchema { .. }
310 | ReadParquetSnafu { .. }
311 | ParquetToSchema { .. }
312 | ParseFormat { .. }
313 | MergeSchema { .. } => StatusCode::InvalidArguments,
314
315 JoinHandle { .. }
316 | ReadRecordBatch { .. }
317 | WriteRecordBatch { .. }
318 | EncodeRecordBatch { .. }
319 | OrcReader { .. } => StatusCode::Unexpected,
320
321 DataFusion { .. } => StatusCode::Internal,
322 }
323 }
324
325 fn as_any(&self) -> &dyn Any {
326 self
327 }
328
329 fn retry_hint(&self) -> RetryHint {
330 use Error::*;
331
332 match self {
333 BuildBackend { error, .. }
334 | ListObjects { error, .. }
335 | ReadObject { error, .. }
336 | WriteObject { error, .. } => retry_hint_from_opendal_error(error),
337 AsyncWrite { error, .. } => retry_hint_from_io_error(error),
338 WriteParquet { .. } => RetryHint::Retryable,
339 _ => RetryHint::NonRetryable,
340 }
341 }
342}