1use std::fmt;
2
3use snafu::Snafu;
4
5#[non_exhaustive]
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum DeltaReaderPhase {
9 Configuration,
11 TableLocation,
13 Storage,
15 Snapshot,
17 Protocol,
19 Schema,
21 ScanPlanning,
23 DataFileRead,
25 DeletionVector,
27 Transform,
29 Execution,
31 DataFusion,
33}
34
35impl DeltaReaderPhase {
36 pub const fn as_str(self) -> &'static str {
38 match self {
39 Self::Configuration => "configuration",
40 Self::TableLocation => "table_location",
41 Self::Storage => "storage",
42 Self::Snapshot => "snapshot",
43 Self::Protocol => "protocol",
44 Self::Schema => "schema",
45 Self::ScanPlanning => "scan_planning",
46 Self::DataFileRead => "data_file_read",
47 Self::DeletionVector => "deletion_vector",
48 Self::Transform => "transform",
49 Self::Execution => "execution",
50 Self::DataFusion => "datafusion",
51 }
52 }
53}
54
55#[non_exhaustive]
57#[derive(Snafu)]
58#[snafu(visibility(pub(crate)))]
59pub enum DeltaReaderError {
60 #[non_exhaustive]
62 #[snafu(display(
63 "delta reader error: phase=configuration code=invalid_configuration reason={reason}"
64 ))]
65 InvalidConfiguration {
66 reason: &'static str,
68 },
69 #[non_exhaustive]
71 #[snafu(display(
72 "delta reader error: phase=table_location code=invalid_table_location reason={reason}"
73 ))]
74 InvalidTableLocation {
75 reason: &'static str,
77 },
78 #[non_exhaustive]
80 #[snafu(display(
81 "delta reader error: phase=storage code=storage_initialization reason={reason}"
82 ))]
83 StorageInitialization {
84 reason: &'static str,
86 #[snafu(source(from(exact)))]
88 source: Box<dyn std::error::Error + Send + Sync + 'static>,
89 },
90 #[non_exhaustive]
92 #[snafu(display("delta reader error: phase=snapshot code=snapshot_load reason={reason}"))]
93 SnapshotLoad {
94 reason: &'static str,
96 #[snafu(source(from(exact)))]
98 source: Box<dyn std::error::Error + Send + Sync + 'static>,
99 },
100 #[non_exhaustive]
102 #[snafu(display(
103 "delta reader error: phase=protocol code=unsupported_protocol reason={reason}"
104 ))]
105 UnsupportedProtocol {
106 reason: &'static str,
108 },
109 #[non_exhaustive]
111 #[snafu(display("delta reader error: phase=schema code=schema_conversion reason={reason}"))]
112 SchemaConversion {
113 reason: &'static str,
115 #[snafu(source(from(exact)))]
117 source: Box<dyn std::error::Error + Send + Sync + 'static>,
118 },
119 #[non_exhaustive]
121 #[snafu(display(
122 "delta reader error: phase=scan_planning code=invalid_projection reason={reason}"
123 ))]
124 InvalidProjection {
125 reason: &'static str,
127 },
128 #[non_exhaustive]
130 #[snafu(display(
131 "delta reader error: phase=scan_planning code=unsupported_predicate reason={reason}"
132 ))]
133 UnsupportedPredicate {
134 reason: &'static str,
136 },
137 #[non_exhaustive]
139 #[snafu(display("delta reader error: phase=scan_planning code=scan_planning reason={reason}"))]
140 ScanPlanning {
141 reason: &'static str,
143 #[snafu(source(from(exact)))]
145 source: Box<dyn std::error::Error + Send + Sync + 'static>,
146 },
147 #[non_exhaustive]
149 #[snafu(display(
150 "delta reader error: phase=scan_planning code=scan_partition_planning reason={reason}"
151 ))]
152 ScanPartitionPlanning {
153 reason: &'static str,
155 },
156 #[non_exhaustive]
158 #[snafu(display(
159 "delta reader error: phase=data_file_read code=data_file_read reason={reason}"
160 ))]
161 DataFileRead {
162 reason: &'static str,
164 #[snafu(source(from(exact)))]
166 source: Box<dyn std::error::Error + Send + Sync + 'static>,
167 },
168 #[non_exhaustive]
170 #[snafu(display(
171 "delta reader error: phase=deletion_vector code=deletion_vector_read reason={reason}"
172 ))]
173 DeletionVectorRead {
174 reason: &'static str,
176 #[snafu(source(from(exact)))]
178 source: Box<dyn std::error::Error + Send + Sync + 'static>,
179 },
180 #[non_exhaustive]
182 #[snafu(display(
183 "delta reader error: phase=transform code=physical_to_logical_transform reason={reason}"
184 ))]
185 PhysicalToLogicalTransform {
186 reason: &'static str,
188 #[snafu(source(from(exact)))]
190 source: Box<dyn std::error::Error + Send + Sync + 'static>,
191 },
192 #[non_exhaustive]
194 #[snafu(display("delta reader error: phase=execution code=cancelled reason={reason}"))]
195 Cancelled {
196 reason: &'static str,
198 },
199 #[cfg(feature = "datafusion")]
201 #[non_exhaustive]
202 #[snafu(display(
203 "delta reader error: phase=datafusion code=datafusion_adapter reason={reason}"
204 ))]
205 DataFusionAdapter {
206 reason: &'static str,
208 #[snafu(source(from(datafusion::common::DataFusionError, Box::new)))]
210 source: Box<datafusion::common::DataFusionError>,
211 },
212}
213
214impl DeltaReaderError {
215 pub const fn code(&self) -> &'static str {
217 match self {
218 Self::InvalidConfiguration { .. } => "invalid_configuration",
219 Self::InvalidTableLocation { .. } => "invalid_table_location",
220 Self::StorageInitialization { .. } => "storage_initialization",
221 Self::SnapshotLoad { .. } => "snapshot_load",
222 Self::UnsupportedProtocol { .. } => "unsupported_protocol",
223 Self::SchemaConversion { .. } => "schema_conversion",
224 Self::InvalidProjection { .. } => "invalid_projection",
225 Self::UnsupportedPredicate { .. } => "unsupported_predicate",
226 Self::ScanPlanning { .. } => "scan_planning",
227 Self::ScanPartitionPlanning { .. } => "scan_partition_planning",
228 Self::DataFileRead { .. } => "data_file_read",
229 Self::DeletionVectorRead { .. } => "deletion_vector_read",
230 Self::PhysicalToLogicalTransform { .. } => "physical_to_logical_transform",
231 Self::Cancelled { .. } => "cancelled",
232 #[cfg(feature = "datafusion")]
233 Self::DataFusionAdapter { .. } => "datafusion_adapter",
234 }
235 }
236
237 pub const fn phase(&self) -> DeltaReaderPhase {
239 match self {
240 Self::InvalidConfiguration { .. } => DeltaReaderPhase::Configuration,
241 Self::InvalidTableLocation { .. } => DeltaReaderPhase::TableLocation,
242 Self::StorageInitialization { .. } => DeltaReaderPhase::Storage,
243 Self::SnapshotLoad { .. } => DeltaReaderPhase::Snapshot,
244 Self::UnsupportedProtocol { .. } => DeltaReaderPhase::Protocol,
245 Self::SchemaConversion { .. } => DeltaReaderPhase::Schema,
246 Self::InvalidProjection { .. }
247 | Self::UnsupportedPredicate { .. }
248 | Self::ScanPlanning { .. }
249 | Self::ScanPartitionPlanning { .. } => DeltaReaderPhase::ScanPlanning,
250 Self::Cancelled { .. } => DeltaReaderPhase::Execution,
251 Self::DataFileRead { .. } => DeltaReaderPhase::DataFileRead,
252 Self::DeletionVectorRead { .. } => DeltaReaderPhase::DeletionVector,
253 Self::PhysicalToLogicalTransform { .. } => DeltaReaderPhase::Transform,
254 #[cfg(feature = "datafusion")]
255 Self::DataFusionAdapter { .. } => DeltaReaderPhase::DataFusion,
256 }
257 }
258}
259
260impl fmt::Debug for DeltaReaderError {
261 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
262 fmt::Display::fmt(self, formatter)
263 }
264}
265
266#[cfg(test)]
267mod tests {
268 use std::{error::Error as _, io};
269
270 use super::{DeltaReaderError, DeltaReaderPhase};
271
272 #[test]
273 fn phase_names_are_stable() {
274 let cases = [
275 (DeltaReaderPhase::Configuration, "configuration"),
276 (DeltaReaderPhase::TableLocation, "table_location"),
277 (DeltaReaderPhase::Storage, "storage"),
278 (DeltaReaderPhase::Snapshot, "snapshot"),
279 (DeltaReaderPhase::Protocol, "protocol"),
280 (DeltaReaderPhase::Schema, "schema"),
281 (DeltaReaderPhase::ScanPlanning, "scan_planning"),
282 (DeltaReaderPhase::DataFileRead, "data_file_read"),
283 (DeltaReaderPhase::DeletionVector, "deletion_vector"),
284 (DeltaReaderPhase::Transform, "transform"),
285 (DeltaReaderPhase::Execution, "execution"),
286 (DeltaReaderPhase::DataFusion, "datafusion"),
287 ];
288
289 for (phase, expected) in cases {
290 assert_eq!(phase.as_str(), expected);
291 }
292 }
293
294 #[test]
295 fn variants_map_to_stable_accessors_and_sources() {
296 let errors = [
297 (
298 DeltaReaderError::InvalidConfiguration {
299 reason: "invalid_configuration",
300 },
301 "invalid_configuration",
302 DeltaReaderPhase::Configuration,
303 false,
304 ),
305 (
306 DeltaReaderError::InvalidTableLocation {
307 reason: "invalid_table_location",
308 },
309 "invalid_table_location",
310 DeltaReaderPhase::TableLocation,
311 false,
312 ),
313 (
314 DeltaReaderError::StorageInitialization {
315 reason: "storage_initialization",
316 source: dependency_source(),
317 },
318 "storage_initialization",
319 DeltaReaderPhase::Storage,
320 true,
321 ),
322 (
323 DeltaReaderError::SnapshotLoad {
324 reason: "snapshot_load",
325 source: dependency_source(),
326 },
327 "snapshot_load",
328 DeltaReaderPhase::Snapshot,
329 true,
330 ),
331 (
332 DeltaReaderError::UnsupportedProtocol {
333 reason: "unsupported_protocol",
334 },
335 "unsupported_protocol",
336 DeltaReaderPhase::Protocol,
337 false,
338 ),
339 (
340 DeltaReaderError::SchemaConversion {
341 reason: "schema_conversion",
342 source: dependency_source(),
343 },
344 "schema_conversion",
345 DeltaReaderPhase::Schema,
346 true,
347 ),
348 (
349 DeltaReaderError::InvalidProjection {
350 reason: "invalid_projection",
351 },
352 "invalid_projection",
353 DeltaReaderPhase::ScanPlanning,
354 false,
355 ),
356 (
357 DeltaReaderError::UnsupportedPredicate {
358 reason: "unsupported_predicate",
359 },
360 "unsupported_predicate",
361 DeltaReaderPhase::ScanPlanning,
362 false,
363 ),
364 (
365 DeltaReaderError::ScanPlanning {
366 reason: "scan_planning",
367 source: dependency_source(),
368 },
369 "scan_planning",
370 DeltaReaderPhase::ScanPlanning,
371 true,
372 ),
373 (
374 DeltaReaderError::ScanPartitionPlanning {
375 reason: "scan_partition_planning",
376 },
377 "scan_partition_planning",
378 DeltaReaderPhase::ScanPlanning,
379 false,
380 ),
381 (
382 DeltaReaderError::DataFileRead {
383 reason: "data_file_read",
384 source: dependency_source(),
385 },
386 "data_file_read",
387 DeltaReaderPhase::DataFileRead,
388 true,
389 ),
390 (
391 DeltaReaderError::DeletionVectorRead {
392 reason: "deletion_vector_read",
393 source: dependency_source(),
394 },
395 "deletion_vector_read",
396 DeltaReaderPhase::DeletionVector,
397 true,
398 ),
399 (
400 DeltaReaderError::PhysicalToLogicalTransform {
401 reason: "physical_to_logical_transform",
402 source: dependency_source(),
403 },
404 "physical_to_logical_transform",
405 DeltaReaderPhase::Transform,
406 true,
407 ),
408 (
409 DeltaReaderError::Cancelled {
410 reason: "cancelled",
411 },
412 "cancelled",
413 DeltaReaderPhase::Execution,
414 false,
415 ),
416 #[cfg(feature = "datafusion")]
417 (
418 DeltaReaderError::DataFusionAdapter {
419 reason: "datafusion_adapter",
420 source: Box::new(datafusion::common::DataFusionError::Execution(
421 "sensitive dependency detail".into(),
422 )),
423 },
424 "datafusion_adapter",
425 DeltaReaderPhase::DataFusion,
426 true,
427 ),
428 ];
429
430 for (error, name, phase, has_source) in errors {
431 assert_eq!(error.source().is_some(), has_source);
432 assert_eq!(error.code(), name);
433 assert_eq!(error.phase(), phase);
434 let display = error.to_string();
435 let debug = format!("{error:?}");
436 assert!(display.contains(&format!("phase={}", phase.as_str())));
437 assert!(display.contains(&format!("code={name}")));
438 assert!(!display.contains("sensitive dependency detail"));
439 assert!(!debug.contains("sensitive dependency detail"));
440 }
441 }
442
443 fn dependency_source() -> Box<dyn std::error::Error + Send + Sync + 'static> {
444 Box::new(io::Error::other("sensitive dependency detail"))
445 }
446
447 #[test]
448 fn boxed_source_preserves_its_concrete_type() {
449 let error = DeltaReaderError::DataFileRead {
450 reason: "data_file_read",
451 source: dependency_source(),
452 };
453
454 assert!(
455 error
456 .source()
457 .and_then(|source| source.downcast_ref::<io::Error>())
458 .is_some()
459 );
460 }
461
462 #[cfg(feature = "datafusion")]
463 #[test]
464 fn datafusion_source_preserves_its_boxed_type() {
465 let error = DeltaReaderError::DataFusionAdapter {
466 reason: "datafusion_adapter",
467 source: Box::new(datafusion::common::DataFusionError::Execution(
468 "failure".into(),
469 )),
470 };
471
472 assert!(
473 error
474 .source()
475 .and_then(|source| {
476 source.downcast_ref::<Box<datafusion::common::DataFusionError>>()
477 })
478 .is_some()
479 );
480 }
481}