1use std::fmt;
5
6use arrow_schema::ArrowError;
7use snafu::{IntoError as _, Location, Snafu};
8
9type BoxedError = Box<dyn std::error::Error + Send + Sync + 'static>;
10
11#[cfg(feature = "backtrace")]
12mod backtrace_support {
13 use std::backtrace::Backtrace;
14
15 use snafu::{AsBacktrace, GenerateImplicitData};
16
17 #[derive(Debug)]
18 pub struct MaybeBacktrace(pub Option<Backtrace>);
19
20 impl GenerateImplicitData for MaybeBacktrace {
21 fn generate() -> Self {
22 Self(<Option<Backtrace>>::generate())
23 }
24 }
25
26 impl AsBacktrace for MaybeBacktrace {
27 fn as_backtrace(&self) -> Option<&Backtrace> {
28 self.0.as_ref()
29 }
30 }
31}
32
33#[cfg(not(feature = "backtrace"))]
34mod backtrace_support {
35 use std::backtrace::Backtrace;
36
37 use snafu::{AsBacktrace, GenerateImplicitData};
38
39 #[derive(Debug)]
40 pub struct MaybeBacktrace;
41
42 impl GenerateImplicitData for MaybeBacktrace {
43 fn generate() -> Self {
44 Self
45 }
46 }
47
48 impl AsBacktrace for MaybeBacktrace {
49 fn as_backtrace(&self) -> Option<&Backtrace> {
50 None
51 }
52 }
53}
54
55use backtrace_support::MaybeBacktrace;
56
57#[derive(Debug)]
62pub struct FieldNotFoundError {
63 pub field_name: String,
64 pub candidates: Vec<String>,
65}
66
67impl fmt::Display for FieldNotFoundError {
68 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
69 write!(f, "Field '{}' not found.", self.field_name)?;
70 let suggestion =
71 crate::levenshtein::find_best_suggestion(&self.field_name, &self.candidates);
72 if let Some(suggestion) = suggestion {
73 write!(f, " Did you mean '{}'?", suggestion)?;
74 }
75 write!(f, "\nAvailable fields: [")?;
76 for (i, candidate) in self.candidates.iter().take(10).enumerate() {
77 if i > 0 {
78 write!(f, ", ")?;
79 }
80 write!(f, "'{}'", candidate)?;
81 }
82 if self.candidates.len() > 10 {
83 let remaining = self.candidates.len() - 10;
84 write!(f, ", ... and {} more]", remaining)?;
85 } else {
86 write!(f, "]")?;
87 }
88 Ok(())
89 }
90}
91
92impl std::error::Error for FieldNotFoundError {}
93
94#[inline]
96pub fn box_error(e: impl std::error::Error + Send + Sync + 'static) -> BoxedError {
97 Box::new(e)
98}
99
100#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
103pub enum FenceReason {
104 PeerClaimedEpoch,
106 PersistenceFailure,
109}
110
111impl std::fmt::Display for FenceReason {
112 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
113 let s = match self {
115 Self::PeerClaimedEpoch => "peer claimed epoch",
116 Self::PersistenceFailure => "persistence failure",
117 };
118 f.write_str(s)
119 }
120}
121
122#[derive(Debug, Snafu)]
123#[snafu(visibility(pub))]
124pub enum Error {
125 #[snafu(display("Invalid user input: {source}, {location}"))]
126 InvalidInput {
127 source: BoxedError,
128 #[snafu(implicit)]
129 location: Location,
130 #[snafu(implicit)]
131 backtrace: MaybeBacktrace,
132 },
133 #[snafu(display("Dataset already exists: {uri}, {location}"))]
134 DatasetAlreadyExists {
135 uri: String,
136 #[snafu(implicit)]
137 location: Location,
138 #[snafu(implicit)]
139 backtrace: MaybeBacktrace,
140 },
141 #[snafu(display("Append with different schema: {difference}, location: {location}"))]
142 SchemaMismatch {
143 difference: String,
144 #[snafu(implicit)]
145 location: Location,
146 #[snafu(implicit)]
147 backtrace: MaybeBacktrace,
148 },
149 #[snafu(display("Dataset at path {path} was not found: {source}, {location}"))]
150 DatasetNotFound {
151 path: String,
152 source: BoxedError,
153 #[snafu(implicit)]
154 location: Location,
155 #[snafu(implicit)]
156 backtrace: MaybeBacktrace,
157 },
158 #[snafu(display("Encountered corrupt file {path}: {source}, {location}"))]
159 CorruptFile {
160 path: object_store::path::Path,
161 source: BoxedError,
162 #[snafu(implicit)]
163 location: Location,
164 #[snafu(implicit)]
165 backtrace: MaybeBacktrace,
166 },
167 #[snafu(display("Not supported: {source}, {location}"))]
168 NotSupported {
169 source: BoxedError,
170 #[snafu(implicit)]
171 location: Location,
172 #[snafu(implicit)]
173 backtrace: MaybeBacktrace,
174 },
175 #[snafu(display("Commit conflict for version {version}: {source}, {location}"))]
176 CommitConflict {
177 version: u64,
178 source: BoxedError,
179 #[snafu(implicit)]
180 location: Location,
181 #[snafu(implicit)]
182 backtrace: MaybeBacktrace,
183 },
184 #[snafu(display("Incompatible transaction: {source}, {location}"))]
185 IncompatibleTransaction {
186 source: BoxedError,
187 #[snafu(implicit)]
188 location: Location,
189 #[snafu(implicit)]
190 backtrace: MaybeBacktrace,
191 },
192 #[snafu(display("Retryable commit conflict for version {version}: {source}, {location}"))]
193 RetryableCommitConflict {
194 version: u64,
195 source: BoxedError,
196 #[snafu(implicit)]
197 location: Location,
198 #[snafu(implicit)]
199 backtrace: MaybeBacktrace,
200 },
201 #[snafu(display("Too many concurrent writers. {message}, {location}"))]
202 TooMuchWriteContention {
203 message: String,
204 #[snafu(implicit)]
205 location: Location,
206 #[snafu(implicit)]
207 backtrace: MaybeBacktrace,
208 },
209 #[snafu(display("Operation timed out: {message}, {location}"))]
210 Timeout {
211 message: String,
212 #[snafu(implicit)]
213 location: Location,
214 },
215 #[snafu(display(
216 "Encountered internal error. Please file a bug report at https://github.com/lance-format/lance/issues. {message}, {location}"
217 ))]
218 Internal {
219 message: String,
220 #[snafu(implicit)]
221 location: Location,
222 #[snafu(implicit)]
223 backtrace: MaybeBacktrace,
224 },
225 #[snafu(display("A prerequisite task failed: {message}, {location}"))]
226 PrerequisiteFailed {
227 message: String,
228 #[snafu(implicit)]
229 location: Location,
230 #[snafu(implicit)]
231 backtrace: MaybeBacktrace,
232 },
233 #[snafu(display("Unprocessable: {message}, {location}"))]
234 Unprocessable {
235 message: String,
236 #[snafu(implicit)]
237 location: Location,
238 #[snafu(implicit)]
239 backtrace: MaybeBacktrace,
240 },
241 #[snafu(display("LanceError(Arrow): {message}, {location}"))]
242 Arrow {
243 message: String,
244 #[snafu(implicit)]
245 location: Location,
246 #[snafu(implicit)]
247 backtrace: MaybeBacktrace,
248 },
249 #[snafu(display("LanceError(Schema): {message}, {location}"))]
250 Schema {
251 message: String,
252 #[snafu(implicit)]
253 location: Location,
254 #[snafu(implicit)]
255 backtrace: MaybeBacktrace,
256 },
257 #[snafu(display("Not found: {uri}, {location}"))]
258 NotFound {
259 uri: String,
260 #[snafu(implicit)]
261 location: Location,
262 #[snafu(implicit)]
263 backtrace: MaybeBacktrace,
264 },
265 #[snafu(display("LanceError(IO): {source}, {location}"))]
266 IO {
267 source: BoxedError,
268 #[snafu(implicit)]
269 location: Location,
270 #[snafu(implicit)]
271 backtrace: MaybeBacktrace,
272 },
273 #[snafu(display("LanceError(Index): {message}, {location}"))]
274 Index {
275 message: String,
276 #[snafu(implicit)]
277 location: Location,
278 #[snafu(implicit)]
279 backtrace: MaybeBacktrace,
280 },
281 #[snafu(display("Lance index not found: {identity}, {location}"))]
282 IndexNotFound {
283 identity: String,
284 #[snafu(implicit)]
285 location: Location,
286 #[snafu(implicit)]
287 backtrace: MaybeBacktrace,
288 },
289 #[snafu(display("Cannot infer storage location from: {message}"))]
290 InvalidTableLocation { message: String },
291 Stop,
293 #[snafu(display("Wrapped error: {error}, {location}"))]
294 Wrapped {
295 #[snafu(source)]
296 error: BoxedError,
297 #[snafu(implicit)]
298 location: Location,
299 #[snafu(implicit)]
300 backtrace: MaybeBacktrace,
301 },
302 #[snafu(display("Cloned error: {message}, {location}"))]
303 Cloned {
304 message: String,
305 #[snafu(implicit)]
306 location: Location,
307 #[snafu(implicit)]
308 backtrace: MaybeBacktrace,
309 },
310 #[snafu(display("Query Execution error: {message}, {location}"))]
311 Execution {
312 message: String,
313 #[snafu(implicit)]
314 location: Location,
315 #[snafu(implicit)]
316 backtrace: MaybeBacktrace,
317 },
318 #[snafu(display("Ref is invalid: {message}"))]
319 InvalidRef { message: String },
320 #[snafu(display("Ref conflict error: {message}"))]
321 RefConflict { message: String },
322 #[snafu(display("Ref not found error: {message}"))]
323 RefNotFound { message: String },
324 #[snafu(display("Cleanup error: {message}"))]
325 Cleanup { message: String },
326 #[snafu(display("Version not found error: {message}"))]
327 VersionNotFound { message: String },
328 #[snafu(display("Version conflict error: {message}"))]
329 VersionConflict {
330 message: String,
331 major_version: u16,
332 minor_version: u16,
333 #[snafu(implicit)]
334 location: Location,
335 #[snafu(implicit)]
336 backtrace: MaybeBacktrace,
337 },
338 #[snafu(display("Namespace error: {source}, {location}"))]
339 Namespace {
340 source: BoxedError,
341 #[snafu(implicit)]
342 location: Location,
343 #[snafu(implicit)]
344 backtrace: MaybeBacktrace,
345 },
346 #[snafu(transparent)]
352 External { source: BoxedError },
353
354 #[snafu(transparent)]
356 FieldNotFound { source: FieldNotFoundError },
357
358 #[snafu(display(
359 "Spill disk cap of {cap_bytes} bytes exceeded; currently using {used_bytes} bytes, {location}"
360 ))]
361 DiskCapExceeded {
362 cap_bytes: u64,
363 used_bytes: u64,
364 #[snafu(implicit)]
365 location: Location,
366 },
367 #[snafu(display("Writer fenced ({reason}): {message}, {location}"))]
371 Fenced {
372 reason: FenceReason,
373 message: String,
374 #[snafu(implicit)]
375 location: Location,
376 },
377}
378
379impl Error {
380 #[cfg(feature = "backtrace")]
385 pub fn backtrace(&self) -> Option<&std::backtrace::Backtrace> {
386 match self {
387 Self::InvalidInput { backtrace, .. }
388 | Self::DatasetAlreadyExists { backtrace, .. }
389 | Self::SchemaMismatch { backtrace, .. }
390 | Self::DatasetNotFound { backtrace, .. }
391 | Self::CorruptFile { backtrace, .. }
392 | Self::NotSupported { backtrace, .. }
393 | Self::CommitConflict { backtrace, .. }
394 | Self::IncompatibleTransaction { backtrace, .. }
395 | Self::RetryableCommitConflict { backtrace, .. }
396 | Self::TooMuchWriteContention { backtrace, .. }
397 | Self::Internal { backtrace, .. }
398 | Self::PrerequisiteFailed { backtrace, .. }
399 | Self::Unprocessable { backtrace, .. }
400 | Self::Arrow { backtrace, .. }
401 | Self::Schema { backtrace, .. }
402 | Self::NotFound { backtrace, .. }
403 | Self::IO { backtrace, .. }
404 | Self::Index { backtrace, .. }
405 | Self::IndexNotFound { backtrace, .. }
406 | Self::Wrapped { backtrace, .. }
407 | Self::Cloned { backtrace, .. }
408 | Self::Execution { backtrace, .. }
409 | Self::VersionConflict { backtrace, .. }
410 | Self::Namespace { backtrace, .. } => {
411 use snafu::AsBacktrace;
412 backtrace.as_backtrace()
413 }
414 Self::InvalidTableLocation { .. }
417 | Self::Stop
418 | Self::InvalidRef { .. }
419 | Self::RefConflict { .. }
420 | Self::RefNotFound { .. }
421 | Self::Cleanup { .. }
422 | Self::VersionNotFound { .. }
423 | Self::External { .. }
424 | Self::FieldNotFound { .. }
425 | Self::Timeout { .. }
426 | Self::DiskCapExceeded { .. }
427 | Self::Fenced { .. } => None,
428 }
429 }
430
431 #[cfg(not(feature = "backtrace"))]
435 pub fn backtrace(&self) -> Option<&std::backtrace::Backtrace> {
436 None
437 }
438
439 #[track_caller]
440 pub fn corrupt_file(path: object_store::path::Path, message: impl Into<String>) -> Self {
441 CorruptFileSnafu { path }.into_error(message.into().into())
442 }
443
444 #[track_caller]
445 pub fn invalid_input(message: impl Into<String>) -> Self {
446 InvalidInputSnafu.into_error(message.into().into())
447 }
448
449 #[track_caller]
450 pub fn invalid_input_source(source: BoxedError) -> Self {
451 InvalidInputSnafu.into_error(source)
452 }
453
454 #[track_caller]
455 pub fn io(message: impl Into<String>) -> Self {
456 IOSnafu.into_error(message.into().into())
457 }
458
459 #[track_caller]
461 pub fn fenced_by_peer(message: impl Into<String>) -> Self {
462 FencedSnafu {
463 reason: FenceReason::PeerClaimedEpoch,
464 message: message.into(),
465 }
466 .build()
467 }
468
469 #[track_caller]
472 pub fn writer_poisoned(message: impl Into<String>) -> Self {
473 FencedSnafu {
474 reason: FenceReason::PersistenceFailure,
475 message: message.into(),
476 }
477 .build()
478 }
479
480 pub fn fence_reason(&self) -> Option<FenceReason> {
483 match self {
484 Self::Fenced { reason, .. } => Some(*reason),
485 _ => None,
486 }
487 }
488
489 #[track_caller]
490 pub fn io_source(source: BoxedError) -> Self {
491 IOSnafu.into_error(source)
492 }
493
494 #[track_caller]
495 pub fn dataset_already_exists(uri: impl Into<String>) -> Self {
496 DatasetAlreadyExistsSnafu { uri: uri.into() }.build()
497 }
498
499 #[track_caller]
500 pub fn dataset_not_found(path: impl Into<String>, source: BoxedError) -> Self {
501 DatasetNotFoundSnafu { path: path.into() }.into_error(source)
502 }
503
504 #[track_caller]
505 pub fn version_conflict(
506 message: impl Into<String>,
507 major_version: u16,
508 minor_version: u16,
509 ) -> Self {
510 VersionConflictSnafu {
511 message: message.into(),
512 major_version,
513 minor_version,
514 }
515 .build()
516 }
517
518 #[track_caller]
519 pub fn not_found(uri: impl Into<String>) -> Self {
520 NotFoundSnafu { uri: uri.into() }.build()
521 }
522
523 pub fn is_not_found(&self) -> bool {
525 match self {
526 Self::NotFound { .. } => true,
527 Self::IO { source, .. } | Self::Wrapped { error: source, .. } => {
528 error_source_is_not_found(source.as_ref())
529 }
530 _ => false,
531 }
532 }
533
534 #[track_caller]
535 pub fn wrapped(error: BoxedError) -> Self {
536 WrappedSnafu.into_error(error)
537 }
538
539 #[track_caller]
540 pub fn schema(message: impl Into<String>) -> Self {
541 SchemaSnafu {
542 message: message.into(),
543 }
544 .build()
545 }
546
547 #[track_caller]
548 pub fn not_supported(message: impl Into<String>) -> Self {
549 NotSupportedSnafu.into_error(message.into().into())
550 }
551
552 #[track_caller]
553 pub fn not_supported_source(source: BoxedError) -> Self {
554 NotSupportedSnafu.into_error(source)
555 }
556
557 #[track_caller]
558 pub fn internal(message: impl Into<String>) -> Self {
559 InternalSnafu {
560 message: message.into(),
561 }
562 .build()
563 }
564
565 #[track_caller]
566 pub fn timeout(message: impl Into<String>) -> Self {
567 TimeoutSnafu {
568 message: message.into(),
569 }
570 .build()
571 }
572
573 #[track_caller]
574 pub fn namespace(message: impl Into<String>) -> Self {
575 NamespaceSnafu.into_error(message.into().into())
576 }
577
578 #[track_caller]
579 pub fn namespace_source(source: Box<dyn std::error::Error + Send + Sync + 'static>) -> Self {
580 NamespaceSnafu.into_error(source)
581 }
582
583 #[track_caller]
584 pub fn arrow(message: impl Into<String>) -> Self {
585 ArrowSnafu {
586 message: message.into(),
587 }
588 .build()
589 }
590
591 #[track_caller]
592 pub fn execution(message: impl Into<String>) -> Self {
593 ExecutionSnafu {
594 message: message.into(),
595 }
596 .build()
597 }
598
599 #[track_caller]
600 pub fn cloned(message: impl Into<String>) -> Self {
601 ClonedSnafu {
602 message: message.into(),
603 }
604 .build()
605 }
606
607 #[track_caller]
608 pub fn schema_mismatch(difference: impl Into<String>) -> Self {
609 SchemaMismatchSnafu {
610 difference: difference.into(),
611 }
612 .build()
613 }
614
615 #[track_caller]
616 pub fn unprocessable(message: impl Into<String>) -> Self {
617 UnprocessableSnafu {
618 message: message.into(),
619 }
620 .build()
621 }
622
623 #[track_caller]
624 pub fn too_much_write_contention(message: impl Into<String>) -> Self {
625 TooMuchWriteContentionSnafu {
626 message: message.into(),
627 }
628 .build()
629 }
630
631 #[track_caller]
632 pub fn prerequisite_failed(message: impl Into<String>) -> Self {
633 PrerequisiteFailedSnafu {
634 message: message.into(),
635 }
636 .build()
637 }
638
639 #[track_caller]
640 pub fn index(message: impl Into<String>) -> Self {
641 IndexSnafu {
642 message: message.into(),
643 }
644 .build()
645 }
646
647 #[track_caller]
648 pub fn index_not_found(identity: impl Into<String>) -> Self {
649 IndexNotFoundSnafu {
650 identity: identity.into(),
651 }
652 .build()
653 }
654
655 #[track_caller]
656 pub fn commit_conflict_source(version: u64, source: BoxedError) -> Self {
657 CommitConflictSnafu { version }.into_error(source)
658 }
659
660 #[track_caller]
661 pub fn retryable_commit_conflict_source(version: u64, source: BoxedError) -> Self {
662 RetryableCommitConflictSnafu { version }.into_error(source)
663 }
664
665 #[track_caller]
666 pub fn incompatible_transaction_source(source: BoxedError) -> Self {
667 IncompatibleTransactionSnafu.into_error(source)
668 }
669
670 #[track_caller]
671 pub fn disk_cap_exceeded(cap_bytes: u64, used_bytes: u64) -> Self {
672 DiskCapExceededSnafu {
673 cap_bytes,
674 used_bytes,
675 }
676 .build()
677 }
678
679 pub fn external(source: BoxedError) -> Self {
681 Self::External { source }
682 }
683
684 pub fn field_not_found(field_name: impl Into<String>, candidates: Vec<String>) -> Self {
686 Self::FieldNotFound {
687 source: FieldNotFoundError {
688 field_name: field_name.into(),
689 candidates,
690 },
691 }
692 }
693
694 pub fn external_source(&self) -> Option<&BoxedError> {
698 match self {
699 Self::External { source } => Some(source),
700 _ => None,
701 }
702 }
703
704 pub fn into_external(self) -> std::result::Result<BoxedError, Self> {
708 match self {
709 Self::External { source } => Ok(source),
710 other => Err(other),
711 }
712 }
713}
714
715fn error_source_is_not_found(source: &(dyn std::error::Error + 'static)) -> bool {
716 if let Some(error) = source.downcast_ref::<Error>() {
717 return error.is_not_found();
718 }
719 if let Some(error) = source.downcast_ref::<object_store::Error>() {
720 return matches!(error, object_store::Error::NotFound { .. })
721 || std::error::Error::source(error).is_some_and(error_source_is_not_found);
722 }
723 source.source().is_some_and(error_source_is_not_found)
724}
725
726pub trait LanceOptionExt<T> {
727 fn expect_ok(self) -> Result<T>;
731}
732
733impl<T> LanceOptionExt<T> for Option<T> {
734 #[track_caller]
735 fn expect_ok(self) -> Result<T> {
736 self.ok_or_else(|| Error::internal("Expected option to have value"))
737 }
738}
739
740pub type Result<T> = std::result::Result<T, Error>;
741pub type ArrowResult<T> = std::result::Result<T, ArrowError>;
742#[cfg(feature = "datafusion")]
743pub type DataFusionResult<T> = std::result::Result<T, datafusion_common::DataFusionError>;
744
745impl From<ArrowError> for Error {
746 #[track_caller]
747 fn from(e: ArrowError) -> Self {
748 match e {
749 ArrowError::ExternalError(source) => {
750 match source.downcast::<Self>() {
752 Ok(lance_err) => *lance_err,
753 Err(source) => Self::External { source },
754 }
755 }
756 other => Self::arrow(other.to_string()),
757 }
758 }
759}
760
761impl From<&ArrowError> for Error {
762 #[track_caller]
763 fn from(e: &ArrowError) -> Self {
764 Self::arrow(e.to_string())
765 }
766}
767
768impl From<std::io::Error> for Error {
769 #[track_caller]
770 fn from(e: std::io::Error) -> Self {
771 if e.get_ref().is_some_and(|inner| inner.is::<Self>()) {
776 return *e
777 .into_inner()
778 .expect("checked Some above")
779 .downcast::<Self>()
780 .expect("checked type above");
781 }
782 Self::io_source(box_error(e))
783 }
784}
785
786impl From<object_store::Error> for Error {
787 #[track_caller]
788 fn from(e: object_store::Error) -> Self {
789 match e {
790 object_store::Error::NotFound { path, .. } => Self::not_found(path),
792 other => Self::io_source(box_error(other)),
793 }
794 }
795}
796
797impl From<prost::DecodeError> for Error {
798 #[track_caller]
799 fn from(e: prost::DecodeError) -> Self {
800 Self::io_source(box_error(e))
801 }
802}
803
804impl From<prost::EncodeError> for Error {
805 #[track_caller]
806 fn from(e: prost::EncodeError) -> Self {
807 Self::io_source(box_error(e))
808 }
809}
810
811impl From<prost::UnknownEnumValue> for Error {
812 #[track_caller]
813 fn from(e: prost::UnknownEnumValue) -> Self {
814 Self::io_source(box_error(e))
815 }
816}
817
818impl From<tokio::task::JoinError> for Error {
819 #[track_caller]
820 fn from(e: tokio::task::JoinError) -> Self {
821 Self::io_source(box_error(e))
822 }
823}
824
825impl From<object_store::path::Error> for Error {
826 #[track_caller]
827 fn from(e: object_store::path::Error) -> Self {
828 Self::io_source(box_error(e))
829 }
830}
831
832impl From<url::ParseError> for Error {
833 #[track_caller]
834 fn from(e: url::ParseError) -> Self {
835 Self::io_source(box_error(e))
836 }
837}
838
839impl From<serde_json::Error> for Error {
840 #[track_caller]
841 fn from(e: serde_json::Error) -> Self {
842 Self::arrow(e.to_string())
843 }
844}
845
846impl From<Error> for ArrowError {
847 fn from(value: Error) -> Self {
848 match value {
849 Error::External { source } => Self::ExternalError(source),
851 Error::Schema { message, .. } => Self::SchemaError(message),
853 e => Self::ExternalError(Box::new(e)),
855 }
856 }
857}
858
859#[cfg(feature = "datafusion")]
860impl From<datafusion_sql::sqlparser::parser::ParserError> for Error {
861 #[track_caller]
862 fn from(e: datafusion_sql::sqlparser::parser::ParserError) -> Self {
863 Self::io_source(box_error(e))
864 }
865}
866
867#[cfg(feature = "datafusion")]
868impl From<datafusion_sql::sqlparser::tokenizer::TokenizerError> for Error {
869 #[track_caller]
870 fn from(e: datafusion_sql::sqlparser::tokenizer::TokenizerError) -> Self {
871 Self::io_source(box_error(e))
872 }
873}
874
875#[cfg(feature = "datafusion")]
876impl From<Error> for datafusion_common::DataFusionError {
877 #[track_caller]
878 fn from(e: Error) -> Self {
879 Self::External(Box::new(e))
880 }
881}
882
883#[cfg(feature = "datafusion")]
884impl From<datafusion_common::DataFusionError> for Error {
885 #[track_caller]
886 fn from(e: datafusion_common::DataFusionError) -> Self {
887 match e {
888 datafusion_common::DataFusionError::SQL(..)
889 | datafusion_common::DataFusionError::Plan(..)
890 | datafusion_common::DataFusionError::Configuration(..)
891 | datafusion_common::DataFusionError::SchemaError(..) => {
892 Self::invalid_input_source(box_error(e))
893 }
894 datafusion_common::DataFusionError::ArrowError(arrow_err, _) => Self::from(*arrow_err),
895 datafusion_common::DataFusionError::NotImplemented(..) => {
896 Self::not_supported_source(box_error(e))
897 }
898 datafusion_common::DataFusionError::Execution(..) => Self::execution(e.to_string()),
899 datafusion_common::DataFusionError::External(source) => {
900 match source.downcast::<Self>() {
902 Ok(lance_err) => *lance_err,
903 Err(source) => Self::External { source },
904 }
905 }
906 _ => Self::io_source(box_error(e)),
907 }
908 }
909}
910
911impl From<Error> for object_store::Error {
915 fn from(err: Error) -> Self {
916 Self::Generic {
917 store: "N/A",
918 source: Box::new(err),
919 }
920 }
921}
922
923#[track_caller]
924pub fn get_caller_location() -> &'static std::panic::Location<'static> {
925 std::panic::Location::caller()
926}
927
928pub struct CloneableError(pub Error);
936
937struct DisplayError(Error);
938
939impl fmt::Debug for DisplayError {
940 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
941 fmt::Display::fmt(self, f)
942 }
943}
944
945impl fmt::Display for DisplayError {
946 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
947 fmt::Display::fmt(&self.0, f)
948 }
949}
950
951impl std::error::Error for DisplayError {
952 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
953 Some(&self.0)
954 }
955}
956
957impl Clone for CloneableError {
958 #[track_caller]
959 fn clone(&self) -> Self {
960 match &self.0 {
961 Error::NotFound { uri, .. } => Self(Error::wrapped(Box::new(DisplayError(
962 Error::not_found(uri.clone()),
963 )))),
964 error if error.is_not_found() => Self(Error::wrapped(Box::new(DisplayError(
965 Error::not_found(error.to_string()),
966 )))),
967 Error::Timeout { message, .. } => Self(Error::timeout(message.clone())),
968 Error::IO { source, .. } => Self(Error::io(source.to_string())),
969 error => Self(Error::cloned(error.to_string())),
970 }
971 }
972}
973
974#[derive(Clone)]
975pub struct CloneableResult<T: Clone>(pub std::result::Result<T, CloneableError>);
976
977impl<T: Clone> From<Result<T>> for CloneableResult<T> {
978 fn from(result: Result<T>) -> Self {
979 Self(result.map_err(CloneableError))
980 }
981}
982
983#[cfg(test)]
984mod test {
985 use super::*;
986 use std::error::Error as _;
987 use std::fmt;
988
989 #[test]
990 fn cloneable_error_preserves_not_found_contract() {
991 let original = CloneableError(Error::not_found("metadata.lance"));
992 let cloned = original.clone();
993 let cloned_again = cloned.clone();
994 assert!(matches!(original.0, Error::NotFound { .. }));
995 assert!(cloned.0.is_not_found());
996 assert!(cloned_again.0.is_not_found());
997 assert!(cloned.0.to_string().to_lowercase().contains("not found"));
998 assert!(
999 cloned_again
1000 .0
1001 .to_string()
1002 .to_lowercase()
1003 .contains("not found")
1004 );
1005 assert!(
1006 format!("{:?}", cloned.0)
1007 .to_lowercase()
1008 .contains("not found")
1009 );
1010 assert!(cloned.0.source().is_some_and(|source| source.is::<Error>()
1011 || source.source().is_some_and(|source| source.is::<Error>())));
1012 let downstream_error = Error::wrapped(Box::new(Error::io_source(Box::new(
1013 object_store::Error::Generic {
1014 store: "N/A",
1015 source: Box::new(cloned.0),
1016 },
1017 ))));
1018 assert!(downstream_error.is_not_found());
1019 assert!(
1020 format!("{downstream_error:?}")
1021 .to_lowercase()
1022 .contains("not found")
1023 );
1024
1025 let original = CloneableError(Error::timeout("metadata read timed out"));
1026 let cloned = original.clone();
1027 assert!(matches!(original.0, Error::Timeout { .. }));
1028 assert!(matches!(cloned.0, Error::Timeout { .. }));
1029
1030 let original = CloneableError(Error::io("metadata read was denied"));
1031 let cloned = original.clone();
1032 assert!(matches!(original.0, Error::IO { .. }));
1033 assert!(matches!(cloned.0, Error::IO { .. }));
1034 }
1035
1036 #[test]
1037 fn test_caller_location_capture() {
1038 let current_fn = get_caller_location();
1039 let f: Box<dyn Fn() -> Result<()>> = Box::new(|| {
1042 Err(object_store::Error::Generic {
1043 store: "",
1044 source: "".into(),
1045 })?;
1046 Ok(())
1047 });
1048 match f().unwrap_err() {
1049 Error::IO { location, .. } => {
1050 assert_eq!(location.line(), current_fn.line() + 4, "{}", location)
1052 }
1053 #[allow(unreachable_patterns)]
1054 _ => panic!("expected ObjectStore error"),
1055 }
1056 }
1057
1058 #[test]
1059 fn test_caller_location_capture_not_found() {
1060 let current_fn = get_caller_location();
1061 let f: Box<dyn Fn() -> Result<()>> = Box::new(|| {
1062 Err(object_store::Error::NotFound {
1063 path: "some/path".to_string(),
1064 source: "not found".into(),
1065 })?;
1066 Ok(())
1067 });
1068 match f().unwrap_err() {
1069 Error::NotFound { location, .. } => {
1070 assert_eq!(location.line(), current_fn.line() + 2, "{}", location)
1072 }
1073 #[allow(unreachable_patterns)]
1074 other => panic!("expected NotFound, got {:?}", other),
1075 }
1076 }
1077
1078 #[test]
1079 fn test_object_store_not_found_converts_to_not_found() {
1080 let os_err = object_store::Error::NotFound {
1081 path: "test/path".to_string(),
1082 source: "no such file".into(),
1083 };
1084 let lance_err: Error = os_err.into();
1085 match lance_err {
1086 Error::NotFound { uri, .. } => {
1087 assert_eq!(uri, "test/path");
1088 }
1089 other => panic!("Expected NotFound, got {:?}", other),
1090 }
1091 }
1092
1093 #[derive(Debug)]
1094 struct MyCustomError {
1095 code: i32,
1096 message: String,
1097 }
1098
1099 impl fmt::Display for MyCustomError {
1100 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1101 write!(f, "MyCustomError({}): {}", self.code, self.message)
1102 }
1103 }
1104
1105 impl std::error::Error for MyCustomError {}
1106
1107 #[test]
1108 fn test_io_error_recovers_wrapped_lance_error() {
1109 let io_err = std::io::Error::other(Error::disk_cap_exceeded(100, 50));
1112 let recovered: Error = io_err.into();
1113 match recovered {
1114 Error::DiskCapExceeded {
1115 cap_bytes,
1116 used_bytes,
1117 ..
1118 } => {
1119 assert_eq!(cap_bytes, 100);
1120 assert_eq!(used_bytes, 50);
1121 }
1122 other => panic!("expected DiskCapExceeded, got {other:?}"),
1123 }
1124 }
1125
1126 #[test]
1127 fn test_io_error_without_lance_error_stays_io() {
1128 let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "missing");
1130 let converted: Error = io_err.into();
1131 assert!(matches!(converted, Error::IO { .. }));
1132 }
1133
1134 #[test]
1135 fn test_external_error_creation() {
1136 let custom_err = MyCustomError {
1137 code: 42,
1138 message: "test error".to_string(),
1139 };
1140 let err = Error::external(Box::new(custom_err));
1141
1142 match &err {
1143 Error::External { source } => {
1144 let recovered = source.downcast_ref::<MyCustomError>().unwrap();
1145 assert_eq!(recovered.code, 42);
1146 assert_eq!(recovered.message, "test error");
1147 }
1148 _ => panic!("Expected External variant"),
1149 }
1150 }
1151
1152 #[test]
1153 fn test_external_source_method() {
1154 let custom_err = MyCustomError {
1155 code: 123,
1156 message: "source test".to_string(),
1157 };
1158 let err = Error::external(Box::new(custom_err));
1159
1160 let source = err.external_source().expect("should have external source");
1161 let recovered = source.downcast_ref::<MyCustomError>().unwrap();
1162 assert_eq!(recovered.code, 123);
1163
1164 let io_err = Error::io("test");
1166 assert!(io_err.external_source().is_none());
1167 }
1168
1169 #[test]
1170 fn test_into_external_method() {
1171 let custom_err = MyCustomError {
1172 code: 456,
1173 message: "into test".to_string(),
1174 };
1175 let err = Error::external(Box::new(custom_err));
1176
1177 match err.into_external() {
1178 Ok(source) => {
1179 let recovered = source.downcast::<MyCustomError>().unwrap();
1180 assert_eq!(recovered.code, 456);
1181 }
1182 Err(_) => panic!("Expected Ok"),
1183 }
1184
1185 let io_err = Error::io("test");
1187 match io_err.into_external() {
1188 Err(Error::IO { .. }) => {}
1189 _ => panic!("Expected Err with IO variant"),
1190 }
1191 }
1192
1193 #[test]
1194 fn test_arrow_external_error_conversion() {
1195 let custom_err = MyCustomError {
1196 code: 789,
1197 message: "arrow test".to_string(),
1198 };
1199 let arrow_err = ArrowError::ExternalError(Box::new(custom_err));
1200 let lance_err: Error = arrow_err.into();
1201
1202 match lance_err {
1203 Error::External { source } => {
1204 let recovered = source.downcast_ref::<MyCustomError>().unwrap();
1205 assert_eq!(recovered.code, 789);
1206 }
1207 _ => panic!("Expected External variant, got {:?}", lance_err),
1208 }
1209 }
1210
1211 #[test]
1212 fn test_external_to_arrow_roundtrip() {
1213 let custom_err = MyCustomError {
1214 code: 999,
1215 message: "roundtrip".to_string(),
1216 };
1217 let lance_err = Error::external(Box::new(custom_err));
1218 let arrow_err: ArrowError = lance_err.into();
1219
1220 match arrow_err {
1221 ArrowError::ExternalError(source) => {
1222 let recovered = source.downcast_ref::<MyCustomError>().unwrap();
1223 assert_eq!(recovered.code, 999);
1224 }
1225 _ => panic!("Expected ExternalError variant"),
1226 }
1227 }
1228
1229 #[cfg(feature = "datafusion")]
1230 #[test]
1231 fn test_datafusion_schema_error_is_invalid_input() {
1232 use datafusion_common::Column;
1237
1238 let schema_err = datafusion_common::SchemaError::FieldNotFound {
1239 field: Box::new(Column::from_name("missing_col")),
1240 valid_fields: vec![],
1241 };
1242 let df_err =
1243 datafusion_common::DataFusionError::SchemaError(Box::new(schema_err), Box::new(None));
1244 let lance_err: Error = df_err.into();
1245
1246 match lance_err {
1247 Error::InvalidInput { .. } => {
1248 assert!(
1249 lance_err.to_string().contains("missing_col"),
1250 "expected the column name to survive in the error message, got: {lance_err}"
1251 );
1252 }
1253 _ => panic!("Expected InvalidInput variant, got {:?}", lance_err),
1254 }
1255 }
1256
1257 #[cfg(feature = "datafusion")]
1258 #[test]
1259 fn test_datafusion_external_error_conversion() {
1260 let custom_err = MyCustomError {
1261 code: 111,
1262 message: "datafusion test".to_string(),
1263 };
1264 let df_err = datafusion_common::DataFusionError::External(Box::new(custom_err));
1265 let lance_err: Error = df_err.into();
1266
1267 match lance_err {
1268 Error::External { source } => {
1269 let recovered = source.downcast_ref::<MyCustomError>().unwrap();
1270 assert_eq!(recovered.code, 111);
1271 }
1272 _ => panic!("Expected External variant"),
1273 }
1274 }
1275
1276 #[cfg(feature = "datafusion")]
1277 #[test]
1278 fn test_datafusion_arrow_external_error_conversion() {
1279 let custom_err = MyCustomError {
1281 code: 222,
1282 message: "nested test".to_string(),
1283 };
1284 let arrow_err = ArrowError::ExternalError(Box::new(custom_err));
1285 let df_err = datafusion_common::DataFusionError::ArrowError(Box::new(arrow_err), None);
1286 let lance_err: Error = df_err.into();
1287
1288 match lance_err {
1289 Error::External { source } => {
1290 let recovered = source.downcast_ref::<MyCustomError>().unwrap();
1291 assert_eq!(recovered.code, 222);
1292 }
1293 _ => panic!("Expected External variant, got {:?}", lance_err),
1294 }
1295 }
1296
1297 #[test]
1303 fn test_lance_error_roundtrip_through_arrow() {
1304 let original = Error::invalid_input("test validation error");
1305
1306 let arrow_err: ArrowError = original.into();
1308
1309 let recovered: Error = arrow_err.into();
1311
1312 match recovered {
1314 Error::InvalidInput { .. } => {
1315 assert!(recovered.to_string().contains("test validation error"));
1316 }
1317 _ => panic!("Expected InvalidInput variant, got {:?}", recovered),
1318 }
1319 }
1320
1321 #[cfg(feature = "datafusion")]
1327 #[test]
1328 fn test_lance_error_roundtrip_through_datafusion() {
1329 let original = Error::invalid_input("test validation error");
1330
1331 let df_err: datafusion_common::DataFusionError = original.into();
1333
1334 let recovered: Error = df_err.into();
1336
1337 match recovered {
1339 Error::InvalidInput { .. } => {
1340 assert!(recovered.to_string().contains("test validation error"));
1341 }
1342 _ => panic!("Expected InvalidInput variant, got {:?}", recovered),
1343 }
1344 }
1345
1346 #[test]
1347 fn test_backtrace_accessor() {
1348 let err = Error::io("test backtrace");
1350 let bt = err.backtrace();
1351 #[cfg(feature = "backtrace")]
1352 {
1353 let _ = bt;
1357 }
1358 #[cfg(not(feature = "backtrace"))]
1359 {
1360 assert!(bt.is_none());
1362 }
1363 }
1364
1365 #[test]
1366 fn test_backtrace_captured_when_feature_enabled() {
1367 #[cfg(feature = "backtrace")]
1376 {
1377 let err = Error::io("backtrace capture test");
1378 if std::env::var("RUST_BACKTRACE").is_ok() {
1379 assert!(
1380 err.backtrace().is_some(),
1381 "Expected a backtrace when RUST_BACKTRACE=1 and backtrace feature is enabled"
1382 );
1383 }
1384 }
1387 #[cfg(not(feature = "backtrace"))]
1388 {
1389 let err = Error::io("backtrace capture test");
1390 assert!(err.backtrace().is_none());
1391 }
1392 }
1393
1394 #[test]
1395 fn test_backtrace_returns_none_for_variants_without_location() {
1396 let err = Error::InvalidTableLocation {
1397 message: "test".to_string(),
1398 };
1399 assert!(err.backtrace().is_none());
1400
1401 let err = Error::InvalidRef {
1402 message: "test".to_string(),
1403 };
1404 assert!(err.backtrace().is_none());
1405
1406 let err = Error::Stop;
1407 assert!(err.backtrace().is_none());
1408 }
1409}