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]
452 pub fn corrupt_file_named(name: &str, message: impl Into<String>) -> Self {
453 Self::corrupt_file(object_store::path::Path::from(name), message)
454 }
455
456 #[track_caller]
457 pub fn invalid_input(message: impl Into<String>) -> Self {
458 InvalidInputSnafu.into_error(message.into().into())
459 }
460
461 #[track_caller]
462 pub fn invalid_input_source(source: BoxedError) -> Self {
463 InvalidInputSnafu.into_error(source)
464 }
465
466 #[track_caller]
467 pub fn io(message: impl Into<String>) -> Self {
468 IOSnafu.into_error(message.into().into())
469 }
470
471 #[track_caller]
473 pub fn fenced_by_peer(message: impl Into<String>) -> Self {
474 FencedSnafu {
475 reason: FenceReason::PeerClaimedEpoch,
476 message: message.into(),
477 }
478 .build()
479 }
480
481 #[track_caller]
484 pub fn writer_poisoned(message: impl Into<String>) -> Self {
485 FencedSnafu {
486 reason: FenceReason::PersistenceFailure,
487 message: message.into(),
488 }
489 .build()
490 }
491
492 pub fn fence_reason(&self) -> Option<FenceReason> {
495 match self {
496 Self::Fenced { reason, .. } => Some(*reason),
497 _ => None,
498 }
499 }
500
501 #[track_caller]
502 pub fn io_source(source: BoxedError) -> Self {
503 IOSnafu.into_error(source)
504 }
505
506 #[track_caller]
507 pub fn dataset_already_exists(uri: impl Into<String>) -> Self {
508 DatasetAlreadyExistsSnafu { uri: uri.into() }.build()
509 }
510
511 #[track_caller]
512 pub fn dataset_not_found(path: impl Into<String>, source: BoxedError) -> Self {
513 DatasetNotFoundSnafu { path: path.into() }.into_error(source)
514 }
515
516 #[track_caller]
517 pub fn version_conflict(
518 message: impl Into<String>,
519 major_version: u16,
520 minor_version: u16,
521 ) -> Self {
522 VersionConflictSnafu {
523 message: message.into(),
524 major_version,
525 minor_version,
526 }
527 .build()
528 }
529
530 #[track_caller]
531 pub fn not_found(uri: impl Into<String>) -> Self {
532 NotFoundSnafu { uri: uri.into() }.build()
533 }
534
535 pub fn is_not_found(&self) -> bool {
537 match self {
538 Self::NotFound { .. } => true,
539 Self::IO { source, .. } | Self::Wrapped { error: source, .. } => {
540 error_source_is_not_found(source.as_ref())
541 }
542 _ => false,
543 }
544 }
545
546 #[track_caller]
547 pub fn wrapped(error: BoxedError) -> Self {
548 WrappedSnafu.into_error(error)
549 }
550
551 #[track_caller]
552 pub fn schema(message: impl Into<String>) -> Self {
553 SchemaSnafu {
554 message: message.into(),
555 }
556 .build()
557 }
558
559 #[track_caller]
560 pub fn not_supported(message: impl Into<String>) -> Self {
561 NotSupportedSnafu.into_error(message.into().into())
562 }
563
564 #[track_caller]
565 pub fn not_supported_source(source: BoxedError) -> Self {
566 NotSupportedSnafu.into_error(source)
567 }
568
569 #[track_caller]
570 pub fn internal(message: impl Into<String>) -> Self {
571 InternalSnafu {
572 message: message.into(),
573 }
574 .build()
575 }
576
577 #[track_caller]
578 pub fn timeout(message: impl Into<String>) -> Self {
579 TimeoutSnafu {
580 message: message.into(),
581 }
582 .build()
583 }
584
585 #[track_caller]
586 pub fn namespace(message: impl Into<String>) -> Self {
587 NamespaceSnafu.into_error(message.into().into())
588 }
589
590 #[track_caller]
591 pub fn namespace_source(source: Box<dyn std::error::Error + Send + Sync + 'static>) -> Self {
592 NamespaceSnafu.into_error(source)
593 }
594
595 #[track_caller]
596 pub fn arrow(message: impl Into<String>) -> Self {
597 ArrowSnafu {
598 message: message.into(),
599 }
600 .build()
601 }
602
603 #[track_caller]
604 pub fn execution(message: impl Into<String>) -> Self {
605 ExecutionSnafu {
606 message: message.into(),
607 }
608 .build()
609 }
610
611 #[track_caller]
612 pub fn cloned(message: impl Into<String>) -> Self {
613 ClonedSnafu {
614 message: message.into(),
615 }
616 .build()
617 }
618
619 #[track_caller]
620 pub fn schema_mismatch(difference: impl Into<String>) -> Self {
621 SchemaMismatchSnafu {
622 difference: difference.into(),
623 }
624 .build()
625 }
626
627 #[track_caller]
628 pub fn unprocessable(message: impl Into<String>) -> Self {
629 UnprocessableSnafu {
630 message: message.into(),
631 }
632 .build()
633 }
634
635 #[track_caller]
636 pub fn too_much_write_contention(message: impl Into<String>) -> Self {
637 TooMuchWriteContentionSnafu {
638 message: message.into(),
639 }
640 .build()
641 }
642
643 #[track_caller]
644 pub fn prerequisite_failed(message: impl Into<String>) -> Self {
645 PrerequisiteFailedSnafu {
646 message: message.into(),
647 }
648 .build()
649 }
650
651 #[track_caller]
652 pub fn index(message: impl Into<String>) -> Self {
653 IndexSnafu {
654 message: message.into(),
655 }
656 .build()
657 }
658
659 #[track_caller]
660 pub fn index_not_found(identity: impl Into<String>) -> Self {
661 IndexNotFoundSnafu {
662 identity: identity.into(),
663 }
664 .build()
665 }
666
667 #[track_caller]
668 pub fn commit_conflict_source(version: u64, source: BoxedError) -> Self {
669 CommitConflictSnafu { version }.into_error(source)
670 }
671
672 #[track_caller]
673 pub fn retryable_commit_conflict_source(version: u64, source: BoxedError) -> Self {
674 RetryableCommitConflictSnafu { version }.into_error(source)
675 }
676
677 #[track_caller]
678 pub fn incompatible_transaction_source(source: BoxedError) -> Self {
679 IncompatibleTransactionSnafu.into_error(source)
680 }
681
682 #[track_caller]
683 pub fn disk_cap_exceeded(cap_bytes: u64, used_bytes: u64) -> Self {
684 DiskCapExceededSnafu {
685 cap_bytes,
686 used_bytes,
687 }
688 .build()
689 }
690
691 pub fn external(source: BoxedError) -> Self {
693 Self::External { source }
694 }
695
696 pub fn field_not_found(field_name: impl Into<String>, candidates: Vec<String>) -> Self {
698 Self::FieldNotFound {
699 source: FieldNotFoundError {
700 field_name: field_name.into(),
701 candidates,
702 },
703 }
704 }
705
706 pub fn external_source(&self) -> Option<&BoxedError> {
710 match self {
711 Self::External { source } => Some(source),
712 _ => None,
713 }
714 }
715
716 pub fn into_external(self) -> std::result::Result<BoxedError, Self> {
720 match self {
721 Self::External { source } => Ok(source),
722 other => Err(other),
723 }
724 }
725}
726
727fn error_source_is_not_found(source: &(dyn std::error::Error + 'static)) -> bool {
728 if let Some(error) = source.downcast_ref::<Error>() {
729 return error.is_not_found();
730 }
731 if let Some(error) = source.downcast_ref::<object_store::Error>() {
732 return matches!(error, object_store::Error::NotFound { .. })
733 || std::error::Error::source(error).is_some_and(error_source_is_not_found);
734 }
735 source.source().is_some_and(error_source_is_not_found)
736}
737
738pub trait LanceOptionExt<T> {
739 fn expect_ok(self) -> Result<T>;
743}
744
745impl<T> LanceOptionExt<T> for Option<T> {
746 #[track_caller]
747 fn expect_ok(self) -> Result<T> {
748 self.ok_or_else(|| Error::internal("Expected option to have value"))
749 }
750}
751
752pub type Result<T> = std::result::Result<T, Error>;
753pub type ArrowResult<T> = std::result::Result<T, ArrowError>;
754#[cfg(feature = "datafusion")]
755pub type DataFusionResult<T> = std::result::Result<T, datafusion_common::DataFusionError>;
756
757impl From<ArrowError> for Error {
758 #[track_caller]
759 fn from(e: ArrowError) -> Self {
760 match e {
761 ArrowError::ExternalError(source) => {
762 match source.downcast::<Self>() {
764 Ok(lance_err) => *lance_err,
765 Err(source) => Self::External { source },
766 }
767 }
768 other => Self::arrow(other.to_string()),
769 }
770 }
771}
772
773impl From<&ArrowError> for Error {
774 #[track_caller]
775 fn from(e: &ArrowError) -> Self {
776 Self::arrow(e.to_string())
777 }
778}
779
780impl From<std::io::Error> for Error {
781 #[track_caller]
782 fn from(e: std::io::Error) -> Self {
783 if e.get_ref().is_some_and(|inner| inner.is::<Self>()) {
788 return *e
789 .into_inner()
790 .expect("checked Some above")
791 .downcast::<Self>()
792 .expect("checked type above");
793 }
794 Self::io_source(box_error(e))
795 }
796}
797
798impl From<object_store::Error> for Error {
799 #[track_caller]
800 fn from(e: object_store::Error) -> Self {
801 match e {
802 object_store::Error::NotFound { path, .. } => Self::not_found(path),
804 other => Self::io_source(box_error(other)),
805 }
806 }
807}
808
809impl From<prost::DecodeError> for Error {
810 #[track_caller]
811 fn from(e: prost::DecodeError) -> Self {
812 Self::io_source(box_error(e))
813 }
814}
815
816impl From<prost::EncodeError> for Error {
817 #[track_caller]
818 fn from(e: prost::EncodeError) -> Self {
819 Self::io_source(box_error(e))
820 }
821}
822
823impl From<prost::UnknownEnumValue> for Error {
824 #[track_caller]
825 fn from(e: prost::UnknownEnumValue) -> Self {
826 Self::io_source(box_error(e))
827 }
828}
829
830impl From<tokio::task::JoinError> for Error {
831 #[track_caller]
832 fn from(e: tokio::task::JoinError) -> Self {
833 Self::io_source(box_error(e))
834 }
835}
836
837impl From<object_store::path::Error> for Error {
838 #[track_caller]
839 fn from(e: object_store::path::Error) -> Self {
840 Self::io_source(box_error(e))
841 }
842}
843
844impl From<url::ParseError> for Error {
845 #[track_caller]
846 fn from(e: url::ParseError) -> Self {
847 Self::io_source(box_error(e))
848 }
849}
850
851impl From<serde_json::Error> for Error {
852 #[track_caller]
853 fn from(e: serde_json::Error) -> Self {
854 Self::arrow(e.to_string())
855 }
856}
857
858impl From<Error> for ArrowError {
859 fn from(value: Error) -> Self {
860 match value {
861 Error::External { source } => Self::ExternalError(source),
863 Error::Schema { message, .. } => Self::SchemaError(message),
865 e => Self::ExternalError(Box::new(e)),
867 }
868 }
869}
870
871#[cfg(feature = "datafusion")]
872impl From<datafusion_sql::sqlparser::parser::ParserError> for Error {
873 #[track_caller]
874 fn from(e: datafusion_sql::sqlparser::parser::ParserError) -> Self {
875 Self::io_source(box_error(e))
876 }
877}
878
879#[cfg(feature = "datafusion")]
880impl From<datafusion_sql::sqlparser::tokenizer::TokenizerError> for Error {
881 #[track_caller]
882 fn from(e: datafusion_sql::sqlparser::tokenizer::TokenizerError) -> Self {
883 Self::io_source(box_error(e))
884 }
885}
886
887#[cfg(feature = "datafusion")]
888impl From<Error> for datafusion_common::DataFusionError {
889 #[track_caller]
890 fn from(e: Error) -> Self {
891 Self::External(Box::new(e))
892 }
893}
894
895#[cfg(feature = "datafusion")]
896impl From<datafusion_common::DataFusionError> for Error {
897 #[track_caller]
898 fn from(e: datafusion_common::DataFusionError) -> Self {
899 match e {
900 datafusion_common::DataFusionError::SQL(..)
901 | datafusion_common::DataFusionError::Plan(..)
902 | datafusion_common::DataFusionError::Configuration(..)
903 | datafusion_common::DataFusionError::SchemaError(..) => {
904 Self::invalid_input_source(box_error(e))
905 }
906 datafusion_common::DataFusionError::ArrowError(arrow_err, _) => Self::from(*arrow_err),
907 datafusion_common::DataFusionError::NotImplemented(..) => {
908 Self::not_supported_source(box_error(e))
909 }
910 datafusion_common::DataFusionError::Execution(..) => Self::execution(e.to_string()),
911 datafusion_common::DataFusionError::Shared(shared) => {
912 match std::sync::Arc::try_unwrap(shared) {
918 Ok(inner) => Self::from(inner),
919 Err(shared) => Self::execution(shared.to_string()),
920 }
921 }
922 datafusion_common::DataFusionError::External(source) => {
923 match source.downcast::<Self>() {
925 Ok(lance_err) => *lance_err,
926 Err(source) => Self::External { source },
927 }
928 }
929 _ => Self::io_source(box_error(e)),
930 }
931 }
932}
933
934impl From<Error> for object_store::Error {
938 fn from(err: Error) -> Self {
939 Self::Generic {
940 store: "N/A",
941 source: Box::new(err),
942 }
943 }
944}
945
946#[track_caller]
947pub fn get_caller_location() -> &'static std::panic::Location<'static> {
948 std::panic::Location::caller()
949}
950
951pub struct CloneableError(pub Error);
959
960struct DisplayError(Error);
961
962impl fmt::Debug for DisplayError {
963 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
964 fmt::Display::fmt(self, f)
965 }
966}
967
968impl fmt::Display for DisplayError {
969 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
970 fmt::Display::fmt(&self.0, f)
971 }
972}
973
974impl std::error::Error for DisplayError {
975 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
976 Some(&self.0)
977 }
978}
979
980impl Clone for CloneableError {
981 #[track_caller]
982 fn clone(&self) -> Self {
983 match &self.0 {
984 Error::NotFound { uri, .. } => Self(Error::wrapped(Box::new(DisplayError(
985 Error::not_found(uri.clone()),
986 )))),
987 error if error.is_not_found() => Self(Error::wrapped(Box::new(DisplayError(
988 Error::not_found(error.to_string()),
989 )))),
990 Error::Timeout { message, .. } => Self(Error::timeout(message.clone())),
991 Error::IO { source, .. } => Self(Error::io(source.to_string())),
992 error => Self(Error::cloned(error.to_string())),
993 }
994 }
995}
996
997#[derive(Clone)]
998pub struct CloneableResult<T: Clone>(pub std::result::Result<T, CloneableError>);
999
1000impl<T: Clone> From<Result<T>> for CloneableResult<T> {
1001 fn from(result: Result<T>) -> Self {
1002 Self(result.map_err(CloneableError))
1003 }
1004}
1005
1006#[cfg(test)]
1007mod test {
1008 use super::*;
1009 use std::error::Error as _;
1010 use std::fmt;
1011
1012 #[test]
1013 fn cloneable_error_preserves_not_found_contract() {
1014 let original = CloneableError(Error::not_found("metadata.lance"));
1015 let cloned = original.clone();
1016 let cloned_again = cloned.clone();
1017 assert!(matches!(original.0, Error::NotFound { .. }));
1018 assert!(cloned.0.is_not_found());
1019 assert!(cloned_again.0.is_not_found());
1020 assert!(cloned.0.to_string().to_lowercase().contains("not found"));
1021 assert!(
1022 cloned_again
1023 .0
1024 .to_string()
1025 .to_lowercase()
1026 .contains("not found")
1027 );
1028 assert!(
1029 format!("{:?}", cloned.0)
1030 .to_lowercase()
1031 .contains("not found")
1032 );
1033 assert!(cloned.0.source().is_some_and(|source| source.is::<Error>()
1034 || source.source().is_some_and(|source| source.is::<Error>())));
1035 let downstream_error = Error::wrapped(Box::new(Error::io_source(Box::new(
1036 object_store::Error::Generic {
1037 store: "N/A",
1038 source: Box::new(cloned.0),
1039 },
1040 ))));
1041 assert!(downstream_error.is_not_found());
1042 assert!(
1043 format!("{downstream_error:?}")
1044 .to_lowercase()
1045 .contains("not found")
1046 );
1047
1048 let original = CloneableError(Error::timeout("metadata read timed out"));
1049 let cloned = original.clone();
1050 assert!(matches!(original.0, Error::Timeout { .. }));
1051 assert!(matches!(cloned.0, Error::Timeout { .. }));
1052
1053 let original = CloneableError(Error::io("metadata read was denied"));
1054 let cloned = original.clone();
1055 assert!(matches!(original.0, Error::IO { .. }));
1056 assert!(matches!(cloned.0, Error::IO { .. }));
1057 }
1058
1059 #[test]
1060 fn test_caller_location_capture() {
1061 let current_fn = get_caller_location();
1062 let f: Box<dyn Fn() -> Result<()>> = Box::new(|| {
1065 Err(object_store::Error::Generic {
1066 store: "",
1067 source: "".into(),
1068 })?;
1069 Ok(())
1070 });
1071 match f().unwrap_err() {
1072 Error::IO { location, .. } => {
1073 assert_eq!(location.line(), current_fn.line() + 4, "{}", location)
1075 }
1076 #[allow(unreachable_patterns)]
1077 _ => panic!("expected ObjectStore error"),
1078 }
1079 }
1080
1081 #[test]
1082 fn test_caller_location_capture_not_found() {
1083 let current_fn = get_caller_location();
1084 let f: Box<dyn Fn() -> Result<()>> = Box::new(|| {
1085 Err(object_store::Error::NotFound {
1086 path: "some/path".to_string(),
1087 source: "not found".into(),
1088 })?;
1089 Ok(())
1090 });
1091 match f().unwrap_err() {
1092 Error::NotFound { location, .. } => {
1093 assert_eq!(location.line(), current_fn.line() + 2, "{}", location)
1095 }
1096 #[allow(unreachable_patterns)]
1097 other => panic!("expected NotFound, got {:?}", other),
1098 }
1099 }
1100
1101 #[test]
1102 fn test_object_store_not_found_converts_to_not_found() {
1103 let os_err = object_store::Error::NotFound {
1104 path: "test/path".to_string(),
1105 source: "no such file".into(),
1106 };
1107 let lance_err: Error = os_err.into();
1108 match lance_err {
1109 Error::NotFound { uri, .. } => {
1110 assert_eq!(uri, "test/path");
1111 }
1112 other => panic!("Expected NotFound, got {:?}", other),
1113 }
1114 }
1115
1116 #[derive(Debug)]
1117 struct MyCustomError {
1118 code: i32,
1119 message: String,
1120 }
1121
1122 impl fmt::Display for MyCustomError {
1123 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1124 write!(f, "MyCustomError({}): {}", self.code, self.message)
1125 }
1126 }
1127
1128 impl std::error::Error for MyCustomError {}
1129
1130 #[test]
1131 fn test_io_error_recovers_wrapped_lance_error() {
1132 let io_err = std::io::Error::other(Error::disk_cap_exceeded(100, 50));
1135 let recovered: Error = io_err.into();
1136 match recovered {
1137 Error::DiskCapExceeded {
1138 cap_bytes,
1139 used_bytes,
1140 ..
1141 } => {
1142 assert_eq!(cap_bytes, 100);
1143 assert_eq!(used_bytes, 50);
1144 }
1145 other => panic!("expected DiskCapExceeded, got {other:?}"),
1146 }
1147 }
1148
1149 #[test]
1150 fn test_io_error_without_lance_error_stays_io() {
1151 let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "missing");
1153 let converted: Error = io_err.into();
1154 assert!(matches!(converted, Error::IO { .. }));
1155 }
1156
1157 #[test]
1158 fn test_external_error_creation() {
1159 let custom_err = MyCustomError {
1160 code: 42,
1161 message: "test error".to_string(),
1162 };
1163 let err = Error::external(Box::new(custom_err));
1164
1165 match &err {
1166 Error::External { source } => {
1167 let recovered = source.downcast_ref::<MyCustomError>().unwrap();
1168 assert_eq!(recovered.code, 42);
1169 assert_eq!(recovered.message, "test error");
1170 }
1171 _ => panic!("Expected External variant"),
1172 }
1173 }
1174
1175 #[test]
1176 fn test_external_source_method() {
1177 let custom_err = MyCustomError {
1178 code: 123,
1179 message: "source test".to_string(),
1180 };
1181 let err = Error::external(Box::new(custom_err));
1182
1183 let source = err.external_source().expect("should have external source");
1184 let recovered = source.downcast_ref::<MyCustomError>().unwrap();
1185 assert_eq!(recovered.code, 123);
1186
1187 let io_err = Error::io("test");
1189 assert!(io_err.external_source().is_none());
1190 }
1191
1192 #[test]
1193 fn test_into_external_method() {
1194 let custom_err = MyCustomError {
1195 code: 456,
1196 message: "into test".to_string(),
1197 };
1198 let err = Error::external(Box::new(custom_err));
1199
1200 match err.into_external() {
1201 Ok(source) => {
1202 let recovered = source.downcast::<MyCustomError>().unwrap();
1203 assert_eq!(recovered.code, 456);
1204 }
1205 Err(_) => panic!("Expected Ok"),
1206 }
1207
1208 let io_err = Error::io("test");
1210 match io_err.into_external() {
1211 Err(Error::IO { .. }) => {}
1212 _ => panic!("Expected Err with IO variant"),
1213 }
1214 }
1215
1216 #[test]
1217 fn test_arrow_external_error_conversion() {
1218 let custom_err = MyCustomError {
1219 code: 789,
1220 message: "arrow test".to_string(),
1221 };
1222 let arrow_err = ArrowError::ExternalError(Box::new(custom_err));
1223 let lance_err: Error = arrow_err.into();
1224
1225 match lance_err {
1226 Error::External { source } => {
1227 let recovered = source.downcast_ref::<MyCustomError>().unwrap();
1228 assert_eq!(recovered.code, 789);
1229 }
1230 _ => panic!("Expected External variant, got {:?}", lance_err),
1231 }
1232 }
1233
1234 #[test]
1235 fn test_external_to_arrow_roundtrip() {
1236 let custom_err = MyCustomError {
1237 code: 999,
1238 message: "roundtrip".to_string(),
1239 };
1240 let lance_err = Error::external(Box::new(custom_err));
1241 let arrow_err: ArrowError = lance_err.into();
1242
1243 match arrow_err {
1244 ArrowError::ExternalError(source) => {
1245 let recovered = source.downcast_ref::<MyCustomError>().unwrap();
1246 assert_eq!(recovered.code, 999);
1247 }
1248 _ => panic!("Expected ExternalError variant"),
1249 }
1250 }
1251
1252 #[cfg(feature = "datafusion")]
1253 #[test]
1254 fn test_datafusion_schema_error_is_invalid_input() {
1255 use datafusion_common::Column;
1260
1261 let schema_err = datafusion_common::SchemaError::FieldNotFound {
1262 field: Box::new(Column::from_name("missing_col")),
1263 valid_fields: vec![],
1264 };
1265 let df_err =
1266 datafusion_common::DataFusionError::SchemaError(Box::new(schema_err), Box::new(None));
1267 let lance_err: Error = df_err.into();
1268
1269 match lance_err {
1270 Error::InvalidInput { .. } => {
1271 assert!(
1272 lance_err.to_string().contains("missing_col"),
1273 "expected the column name to survive in the error message, got: {lance_err}"
1274 );
1275 }
1276 _ => panic!("Expected InvalidInput variant, got {:?}", lance_err),
1277 }
1278 }
1279
1280 #[cfg(feature = "datafusion")]
1281 #[test]
1282 fn test_datafusion_external_error_conversion() {
1283 let custom_err = MyCustomError {
1284 code: 111,
1285 message: "datafusion test".to_string(),
1286 };
1287 let df_err = datafusion_common::DataFusionError::External(Box::new(custom_err));
1288 let lance_err: Error = df_err.into();
1289
1290 match lance_err {
1291 Error::External { source } => {
1292 let recovered = source.downcast_ref::<MyCustomError>().unwrap();
1293 assert_eq!(recovered.code, 111);
1294 }
1295 _ => panic!("Expected External variant"),
1296 }
1297 }
1298
1299 #[cfg(feature = "datafusion")]
1300 #[test]
1301 fn test_datafusion_arrow_external_error_conversion() {
1302 let custom_err = MyCustomError {
1304 code: 222,
1305 message: "nested test".to_string(),
1306 };
1307 let arrow_err = ArrowError::ExternalError(Box::new(custom_err));
1308 let df_err = datafusion_common::DataFusionError::ArrowError(Box::new(arrow_err), None);
1309 let lance_err: Error = df_err.into();
1310
1311 match lance_err {
1312 Error::External { source } => {
1313 let recovered = source.downcast_ref::<MyCustomError>().unwrap();
1314 assert_eq!(recovered.code, 222);
1315 }
1316 _ => panic!("Expected External variant, got {:?}", lance_err),
1317 }
1318 }
1319
1320 #[test]
1326 fn test_lance_error_roundtrip_through_arrow() {
1327 let original = Error::invalid_input("test validation error");
1328
1329 let arrow_err: ArrowError = original.into();
1331
1332 let recovered: Error = arrow_err.into();
1334
1335 match recovered {
1337 Error::InvalidInput { .. } => {
1338 assert!(recovered.to_string().contains("test validation error"));
1339 }
1340 _ => panic!("Expected InvalidInput variant, got {:?}", recovered),
1341 }
1342 }
1343
1344 #[cfg(feature = "datafusion")]
1350 #[test]
1351 fn test_lance_error_roundtrip_through_datafusion() {
1352 let original = Error::invalid_input("test validation error");
1353
1354 let df_err: datafusion_common::DataFusionError = original.into();
1356
1357 let recovered: Error = df_err.into();
1359
1360 match recovered {
1362 Error::InvalidInput { .. } => {
1363 assert!(recovered.to_string().contains("test validation error"));
1364 }
1365 _ => panic!("Expected InvalidInput variant, got {:?}", recovered),
1366 }
1367 }
1368
1369 #[test]
1370 fn test_backtrace_accessor() {
1371 let err = Error::io("test backtrace");
1373 let bt = err.backtrace();
1374 #[cfg(feature = "backtrace")]
1375 {
1376 let _ = bt;
1380 }
1381 #[cfg(not(feature = "backtrace"))]
1382 {
1383 assert!(bt.is_none());
1385 }
1386 }
1387
1388 #[test]
1389 fn test_backtrace_captured_when_feature_enabled() {
1390 #[cfg(feature = "backtrace")]
1399 {
1400 let err = Error::io("backtrace capture test");
1401 if std::env::var("RUST_BACKTRACE").is_ok() {
1402 assert!(
1403 err.backtrace().is_some(),
1404 "Expected a backtrace when RUST_BACKTRACE=1 and backtrace feature is enabled"
1405 );
1406 }
1407 }
1410 #[cfg(not(feature = "backtrace"))]
1411 {
1412 let err = Error::io("backtrace capture test");
1413 assert!(err.backtrace().is_none());
1414 }
1415 }
1416
1417 #[test]
1418 fn test_backtrace_returns_none_for_variants_without_location() {
1419 let err = Error::InvalidTableLocation {
1420 message: "test".to_string(),
1421 };
1422 assert!(err.backtrace().is_none());
1423
1424 let err = Error::InvalidRef {
1425 message: "test".to_string(),
1426 };
1427 assert!(err.backtrace().is_none());
1428
1429 let err = Error::Stop;
1430 assert!(err.backtrace().is_none());
1431 }
1432}