timeseries_table_format/table/
error.rs1use snafu::prelude::*;
9
10use super::operations::{
11 AppendError, CoverageQueryError, CreateTableError, OpenTableError, OptimizeError, ScanError,
12 TableStateAccessError,
13};
14
15#[derive(Debug, Snafu)]
21#[snafu(visibility(pub(crate)))]
22#[non_exhaustive]
23pub enum TableError {
24 #[snafu(display("Table creation failed: {source}"))]
26 Create {
27 #[snafu(source, backtrace)]
29 source: CreateTableError,
30 },
31
32 #[snafu(display("Table open failed: {source}"))]
34 Open {
35 #[snafu(source, backtrace)]
37 source: OpenTableError,
38 },
39
40 #[snafu(display("Table state access failed: {source}"))]
42 StateAccess {
43 #[snafu(source, backtrace)]
45 source: TableStateAccessError,
46 },
47
48 #[snafu(display("Append failed: {source}"))]
50 Append {
51 #[snafu(source, backtrace)]
53 source: AppendError,
54 },
55
56 #[snafu(display("Table scan failed: {source}"))]
58 Scan {
59 #[snafu(source, backtrace)]
61 source: ScanError,
62 },
63
64 #[snafu(display("Table coverage query failed: {source}"))]
66 CoverageQuery {
67 #[snafu(source, backtrace)]
69 source: CoverageQueryError,
70 },
71
72 #[snafu(display("Entity-layout optimization failed: {source}"))]
74 Optimize {
75 #[snafu(source, backtrace)]
77 source: OptimizeError,
78 },
79}
80
81#[cfg(test)]
82mod tests {
83 use std::{error::Error as _, io};
84
85 use arrow::error::ArrowError;
86 use parquet::errors::ParquetError;
87 use snafu::{Backtrace, ErrorCompat, IntoError};
88
89 use crate::coverage::{CoverageCodecError, CoverageSidecarError};
90 use crate::formats::parquet::EntityRewriteError;
91 use crate::metadata::{
92 index::IndexSpecError, logical_schema::LogicalToArrowSchemaError,
93 schema_compat::SchemaCompatibilityError,
94 };
95 use crate::storage::StorageError;
96 use crate::transaction_log::CommitError;
97
98 use super::*;
99
100 #[test]
101 fn append_facade_preserves_arrow_source_and_backtrace() {
102 let append_error = AppendError::ArrowInput {
103 source: ArrowError::ComputeError("input failed".to_string()),
104 backtrace: Backtrace::capture(),
105 };
106 let error = AppendSnafu.into_error(append_error);
107
108 let append_source = error
109 .source()
110 .and_then(|source| source.downcast_ref::<AppendError>())
111 .expect("append source");
112 let arrow_source = append_source
113 .source()
114 .and_then(|source| source.downcast_ref::<ArrowError>())
115 .expect("Arrow source");
116 let append_backtrace = ErrorCompat::backtrace(append_source).expect("append backtrace");
117 let table_backtrace = ErrorCompat::backtrace(&error).expect("table backtrace");
118
119 assert!(
120 matches!(arrow_source, ArrowError::ComputeError(message) if message == "input failed")
121 );
122 assert!(std::ptr::eq(table_backtrace, append_backtrace));
123 }
124
125 #[test]
126 fn append_facade_preserves_parquet_source_and_backtrace() {
127 let error = AppendSnafu.into_error(AppendError::ParquetWrite {
128 source: ParquetError::General("write failed".to_string()),
129 backtrace: Backtrace::capture(),
130 });
131
132 let append_source = error
133 .source()
134 .and_then(|source| source.downcast_ref::<AppendError>())
135 .expect("append source");
136 let parquet_source = append_source
137 .source()
138 .and_then(|source| source.downcast_ref::<ParquetError>())
139 .expect("Parquet source");
140 let append_backtrace = ErrorCompat::backtrace(append_source).expect("append backtrace");
141 let table_backtrace = ErrorCompat::backtrace(&error).expect("table backtrace");
142
143 assert!(
144 matches!(parquet_source, ParquetError::General(message) if message == "write failed")
145 );
146 assert!(std::ptr::eq(table_backtrace, append_backtrace));
147 }
148
149 #[test]
150 fn append_schema_validation_leaf_does_not_manufacture_a_backtrace() {
151 let error = AppendError::from(SchemaCompatibilityError::MissingTableSchema);
152
153 assert!(error.to_string().starts_with("Schema validation failed:"));
154 assert!(ErrorCompat::backtrace(&error).is_none());
155 }
156
157 #[test]
158 fn schema_wrappers_delegate_the_originating_backtrace() {
159 let schema = SchemaCompatibilityError::RegisteredSchemaConversion {
160 source: Box::new(LogicalToArrowSchemaError::Int96Unsupported {
161 column: "time".to_string(),
162 backtrace: Backtrace::capture(),
163 }),
164 };
165 let error = AppendSnafu.into_error(AppendError::from(schema));
166
167 let append = error
168 .source()
169 .and_then(|source| source.downcast_ref::<AppendError>())
170 .expect("append source");
171 let schema = append
172 .source()
173 .and_then(|source| source.downcast_ref::<Box<SchemaCompatibilityError>>())
174 .map(Box::as_ref)
175 .expect("schema compatibility source");
176 let conversion = schema
177 .source()
178 .and_then(|source| source.downcast_ref::<Box<LogicalToArrowSchemaError>>())
179 .map(Box::as_ref)
180 .expect("logical-to-Arrow source");
181 let originating_backtrace = ErrorCompat::backtrace(conversion).expect("source backtrace");
182
183 assert!(std::ptr::eq(
184 ErrorCompat::backtrace(schema).expect("schema backtrace"),
185 originating_backtrace
186 ));
187 assert!(std::ptr::eq(
188 ErrorCompat::backtrace(append).expect("append backtrace"),
189 originating_backtrace
190 ));
191 assert!(std::ptr::eq(
192 ErrorCompat::backtrace(&error).expect("table backtrace"),
193 originating_backtrace
194 ));
195 }
196
197 #[test]
198 fn append_rollback_preserves_primary_chain_cleanup_errors_and_backtrace() {
199 let commit_error = CommitError::Conflict {
200 expected: 1,
201 found: 2,
202 backtrace: Backtrace::capture(),
203 };
204 let cleanup_error = StorageError::OtherIo {
205 path: "data/segment.parquet".to_string(),
206 source: io::Error::other("cleanup failed").into(),
207 backtrace: Backtrace::capture(),
208 };
209 let error = AppendSnafu.into_error(AppendError::Rollback {
210 source: Box::new(AppendError::Commit {
211 source: commit_error,
212 }),
213 cleanup_errors: vec![cleanup_error],
214 });
215
216 let rollback = error
217 .source()
218 .and_then(|source| source.downcast_ref::<AppendError>())
219 .expect("rollback source");
220 let cleanup_errors = match rollback {
221 AppendError::Rollback { cleanup_errors, .. } => cleanup_errors,
222 other => panic!("unexpected append error: {other:?}"),
223 };
224 let primary = rollback
225 .source()
226 .and_then(|source| source.downcast_ref::<Box<AppendError>>())
227 .map(Box::as_ref)
228 .expect("primary append source");
229 let commit = primary
230 .source()
231 .and_then(|source| source.downcast_ref::<CommitError>())
232 .expect("commit source");
233 let commit_backtrace = ErrorCompat::backtrace(commit).expect("commit backtrace");
234
235 assert!(matches!(
236 commit,
237 CommitError::Conflict {
238 expected: 1,
239 found: 2,
240 ..
241 }
242 ));
243 assert!(matches!(
244 cleanup_errors.as_slice(),
245 [StorageError::OtherIo { path, .. }] if path == "data/segment.parquet"
246 ));
247 let message = error.to_string();
248 assert!(message.contains("cleanup failed"));
249 assert!(!message.contains("Backtrace"));
250 assert!(std::ptr::eq(
251 ErrorCompat::backtrace(&error).expect("table backtrace"),
252 commit_backtrace
253 ));
254 assert!(std::ptr::eq(
255 ErrorCompat::backtrace(rollback).expect("rollback backtrace"),
256 commit_backtrace
257 ));
258 assert!(std::ptr::eq(
259 ErrorCompat::backtrace(primary).expect("primary backtrace"),
260 commit_backtrace
261 ));
262 }
263
264 #[test]
265 fn create_facade_preserves_index_source_and_operation_backtrace() {
266 let error = CreateSnafu.into_error(CreateTableError::IndexSpecValidation {
267 source: IndexSpecError::EmptyColumn,
268 backtrace: Backtrace::capture(),
269 });
270
271 let create = error
272 .source()
273 .and_then(|source| source.downcast_ref::<CreateTableError>())
274 .expect("create source");
275 let index = create
276 .source()
277 .and_then(|source| source.downcast_ref::<IndexSpecError>())
278 .expect("index specification source");
279
280 assert!(matches!(index, IndexSpecError::EmptyColumn));
281 assert!(std::ptr::eq(
282 ErrorCompat::backtrace(&error).expect("table backtrace"),
283 ErrorCompat::backtrace(create).expect("create backtrace")
284 ));
285 }
286
287 #[test]
288 fn open_facade_preserves_commit_source_and_backtrace() {
289 let error = OpenSnafu.into_error(OpenTableError::Commit {
290 source: CommitError::Conflict {
291 expected: 8,
292 found: 9,
293 backtrace: Backtrace::capture(),
294 },
295 });
296
297 let open = error
298 .source()
299 .and_then(|source| source.downcast_ref::<OpenTableError>())
300 .expect("open source");
301 let commit = open
302 .source()
303 .and_then(|source| source.downcast_ref::<CommitError>())
304 .expect("commit source");
305 let commit_backtrace = ErrorCompat::backtrace(commit).expect("commit backtrace");
306
307 assert!(matches!(commit, CommitError::Conflict { .. }));
308 assert!(std::ptr::eq(
309 ErrorCompat::backtrace(open).expect("open backtrace"),
310 commit_backtrace
311 ));
312 assert!(std::ptr::eq(
313 ErrorCompat::backtrace(&error).expect("table backtrace"),
314 commit_backtrace
315 ));
316 }
317
318 #[test]
319 fn state_access_facade_preserves_commit_source_and_backtrace() {
320 let error = StateAccessSnafu.into_error(TableStateAccessError::Commit {
321 source: CommitError::MissingTableMetadata {
322 current_version: 7,
323 backtrace: Backtrace::capture(),
324 },
325 });
326
327 let state_access = error
328 .source()
329 .and_then(|source| source.downcast_ref::<TableStateAccessError>())
330 .expect("state access source");
331 let commit = state_access
332 .source()
333 .and_then(|source| source.downcast_ref::<CommitError>())
334 .expect("commit source");
335 let commit_backtrace = ErrorCompat::backtrace(commit).expect("commit backtrace");
336
337 assert!(matches!(commit, CommitError::MissingTableMetadata { .. }));
338 assert!(std::ptr::eq(
339 ErrorCompat::backtrace(state_access).expect("state access backtrace"),
340 commit_backtrace
341 ));
342 assert!(std::ptr::eq(
343 ErrorCompat::backtrace(&error).expect("table backtrace"),
344 commit_backtrace
345 ));
346 }
347
348 #[test]
349 fn scan_facade_preserves_parquet_source_and_backtrace() {
350 let error = ScanSnafu.into_error(ScanError::Parquet {
351 path: "data/segment.parquet".to_string(),
352 operation: "reading metadata",
353 source: Box::new(ParquetError::General("corrupt footer".to_string())),
354 backtrace: Box::new(Backtrace::capture()),
355 });
356
357 let scan = error
358 .source()
359 .and_then(|source| source.downcast_ref::<ScanError>())
360 .expect("scan source");
361 let parquet = scan
362 .source()
363 .and_then(|source| source.downcast_ref::<Box<ParquetError>>())
364 .map(Box::as_ref)
365 .expect("Parquet source");
366 let scan_backtrace = ErrorCompat::backtrace(scan).expect("scan backtrace");
367
368 assert!(matches!(parquet, ParquetError::General(message) if message == "corrupt footer"));
369 assert!(std::ptr::eq(
370 ErrorCompat::backtrace(&error).expect("table backtrace"),
371 scan_backtrace
372 ));
373 }
374
375 #[test]
376 fn coverage_facade_preserves_codec_source_and_backtrace() {
377 let error = CoverageQuerySnafu.into_error(CoverageQueryError::CoverageSnapshotRead {
378 coverage_path: "coverage/table.coverage".to_string(),
379 source: Box::new(CoverageSidecarError::Codec {
380 source: CoverageCodecError::InvalidEntityCoverageMagic {
381 backtrace: Backtrace::capture(),
382 },
383 }),
384 });
385
386 let coverage_query = error
387 .source()
388 .and_then(|source| source.downcast_ref::<CoverageQueryError>())
389 .expect("coverage query source");
390 let sidecar = coverage_query
391 .source()
392 .and_then(|source| source.downcast_ref::<Box<CoverageSidecarError>>())
393 .map(Box::as_ref)
394 .expect("coverage sidecar source");
395 let codec = sidecar
396 .source()
397 .and_then(|source| source.downcast_ref::<CoverageCodecError>())
398 .expect("coverage codec source");
399 let codec_backtrace = ErrorCompat::backtrace(codec).expect("codec backtrace");
400
401 assert!(matches!(
402 codec,
403 CoverageCodecError::InvalidEntityCoverageMagic { .. }
404 ));
405 assert!(std::ptr::eq(
406 ErrorCompat::backtrace(coverage_query).expect("coverage query backtrace"),
407 codec_backtrace
408 ));
409 assert!(std::ptr::eq(
410 ErrorCompat::backtrace(&error).expect("table backtrace"),
411 codec_backtrace
412 ));
413 }
414
415 #[test]
416 fn optimize_facade_preserves_commit_source_and_backtrace() {
417 let error = OptimizeSnafu.into_error(OptimizeError::Commit {
418 source: CommitError::Conflict {
419 expected: 3,
420 found: 4,
421 backtrace: Backtrace::capture(),
422 },
423 });
424
425 let optimize = error
426 .source()
427 .and_then(|source| source.downcast_ref::<OptimizeError>())
428 .expect("optimize source");
429 let commit = optimize
430 .source()
431 .and_then(|source| source.downcast_ref::<CommitError>())
432 .expect("commit source");
433 let commit_backtrace = ErrorCompat::backtrace(commit).expect("commit backtrace");
434
435 assert!(matches!(
436 commit,
437 CommitError::Conflict {
438 expected: 3,
439 found: 4,
440 ..
441 }
442 ));
443 assert!(std::ptr::eq(
444 ErrorCompat::backtrace(optimize).expect("optimize backtrace"),
445 commit_backtrace
446 ));
447 assert!(std::ptr::eq(
448 ErrorCompat::backtrace(&error).expect("table backtrace"),
449 commit_backtrace
450 ));
451 }
452
453 #[test]
454 fn optimize_facade_preserves_rewrite_storage_source_and_backtrace() {
455 let error = OptimizeSnafu.into_error(OptimizeError::from(EntityRewriteError::Storage {
456 source: StorageError::OtherIo {
457 path: "data/mixed.parquet".to_string(),
458 source: io::Error::other("read failed").into(),
459 backtrace: Backtrace::capture(),
460 },
461 }));
462
463 let optimize = error
464 .source()
465 .and_then(|source| source.downcast_ref::<OptimizeError>())
466 .expect("optimize source");
467 let rewrite = optimize
468 .source()
469 .and_then(|source| source.downcast_ref::<Box<EntityRewriteError>>())
470 .map(Box::as_ref)
471 .expect("rewrite source");
472 let storage = rewrite
473 .source()
474 .and_then(|source| source.downcast_ref::<StorageError>())
475 .expect("storage source");
476 let storage_backtrace = ErrorCompat::backtrace(storage).expect("storage backtrace");
477
478 assert!(matches!(
479 storage,
480 StorageError::OtherIo { path, .. } if path == "data/mixed.parquet"
481 ));
482 assert!(std::ptr::eq(
483 ErrorCompat::backtrace(rewrite).expect("rewrite backtrace"),
484 storage_backtrace
485 ));
486 assert!(std::ptr::eq(
487 ErrorCompat::backtrace(optimize).expect("optimize backtrace"),
488 storage_backtrace
489 ));
490 assert!(std::ptr::eq(
491 ErrorCompat::backtrace(&error).expect("table backtrace"),
492 storage_backtrace
493 ));
494 }
495
496 #[test]
497 fn optimize_rollback_preserves_primary_chain_and_typed_cleanup_errors() {
498 let error = OptimizeSnafu.into_error(OptimizeError::Rollback {
499 source: Box::new(OptimizeError::Commit {
500 source: CommitError::Conflict {
501 expected: 5,
502 found: 6,
503 backtrace: Backtrace::capture(),
504 },
505 }),
506 cleanup_errors: vec![StorageError::OtherIo {
507 path: "data/_staged/segment.parquet".to_string(),
508 source: io::Error::other("cleanup failed").into(),
509 backtrace: Backtrace::capture(),
510 }],
511 });
512
513 let rollback = error
514 .source()
515 .and_then(|source| source.downcast_ref::<OptimizeError>())
516 .expect("optimize source");
517 let cleanup_errors = match rollback {
518 OptimizeError::Rollback { cleanup_errors, .. } => cleanup_errors,
519 other => panic!("unexpected optimize error: {other:?}"),
520 };
521 let primary = rollback
522 .source()
523 .and_then(|source| source.downcast_ref::<Box<OptimizeError>>())
524 .map(Box::as_ref)
525 .expect("primary optimize source");
526 let commit = primary
527 .source()
528 .and_then(|source| source.downcast_ref::<CommitError>())
529 .expect("commit source");
530 let commit_backtrace = ErrorCompat::backtrace(commit).expect("commit backtrace");
531
532 assert!(matches!(
533 cleanup_errors.as_slice(),
534 [StorageError::OtherIo { path, .. }] if path == "data/_staged/segment.parquet"
535 ));
536 assert!(std::ptr::eq(
537 ErrorCompat::backtrace(rollback).expect("rollback backtrace"),
538 commit_backtrace
539 ));
540 assert!(std::ptr::eq(
541 ErrorCompat::backtrace(&error).expect("table backtrace"),
542 commit_backtrace
543 ));
544 }
545}