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