Skip to main content

delta_kernel/
error.rs

1//! Definitions of errors that the delta kernel can encounter
2
3use std::backtrace::{Backtrace, BacktraceStatus};
4use std::convert::Infallible;
5use std::num::ParseIntError;
6use std::str::Utf8Error;
7
8#[cfg(feature = "default-engine-base")]
9use crate::arrow::error::ArrowError;
10#[cfg(feature = "default-engine-base")]
11use crate::object_store;
12use crate::schema::{DataType, StructType};
13use crate::table_properties::ParseIntervalError;
14use crate::Version;
15
16/// A [`std::result::Result`] that has the kernel [`Error`] as the error variant
17pub type DeltaResult<T, E = Error> = std::result::Result<T, E>;
18
19/// A boxed, `Send` iterator of [`DeltaResult<T>`] items.
20///
21/// Convenience alias for the common pattern of returning a streaming, fallible iterator from
22/// kernel APIs.
23pub type DeltaResultIterator<'a, T> = Box<dyn Iterator<Item = DeltaResult<T>> + Send + 'a>;
24
25/// `'static` counterpart to [`DeltaResultIterator`] for cases where the iterator does not
26/// reference borrowed data.
27pub type DeltaResultIteratorStatic<T> = DeltaResultIterator<'static, T>;
28
29/// All the types of errors that the kernel can run into
30#[non_exhaustive]
31#[derive(thiserror::Error, Debug)]
32pub enum Error {
33    /// This is an error that includes a backtrace. To have a particular type of error include such
34    /// backtrace (when RUST_BACKTRACE=1), annotate the error with `#[error(transparent)]` and then
35    /// add the error type and enum variant to the `from_with_backtrace!` macro invocation
36    /// below. See IOError for an example.
37    #[error("{source}\n{backtrace}")]
38    Backtraced {
39        source: Box<Self>,
40        backtrace: Box<Backtrace>,
41    },
42
43    /// An error performing operations on arrow data
44    #[cfg(feature = "default-engine-base")]
45    #[error(transparent)]
46    Arrow(ArrowError),
47
48    #[error("Error writing checkpoint: {0}")]
49    CheckpointWrite(String),
50
51    /// User tried to convert engine data to the wrong type
52    #[error("Invalid engine data type. Could not convert to {0}")]
53    EngineDataType(String),
54
55    /// Could not extract the specified type
56    #[error("Error extracting type {0}: {1}")]
57    Extract(&'static str, &'static str),
58
59    /// A generic error with a message
60    #[error("Generic delta kernel error: {0}")]
61    Generic(String),
62
63    /// A generic error wrapping another error
64    #[error("Generic error: {source}")]
65    GenericError {
66        /// Source error
67        source: Box<dyn std::error::Error + Send + Sync + 'static>,
68    },
69
70    /// Some kind of [`std::io::Error`]
71    #[error(transparent)]
72    IOError(std::io::Error),
73
74    /// An internal error that means kernel found an unexpected situation, which is likely a bug
75    #[error("Internal error {0}. This is a kernel bug, please report.")]
76    InternalError(String),
77
78    /// An error enountered while working with parquet data
79    #[cfg(feature = "default-engine-base")]
80    #[error("Arrow error: {0}")]
81    Parquet(#[from] crate::parquet::errors::ParquetError),
82
83    /// An error interacting with the object_store crate
84    // We don't use [#from] object_store::Error here as our From impl transforms
85    // object_store::Error::NotFound into Self::FileNotFound
86    #[cfg(feature = "default-engine-base")]
87    #[error("Error interacting with object store: {0}")]
88    ObjectStore(object_store::Error),
89
90    /// An error working with paths from the object_store crate
91    #[cfg(feature = "default-engine-base")]
92    #[error("Object store path error: {0}")]
93    ObjectStorePath(#[from] object_store::path::Error),
94
95    #[cfg(feature = "default-engine-base")]
96    #[error("Reqwest Error: {0}")]
97    Reqwest(#[from] reqwest::Error),
98
99    /// A specified file could not be found
100    #[error("File not found: {0}")]
101    FileNotFound(String),
102
103    /// A column was requested, but not found
104    #[error("{0}")]
105    MissingColumn(String),
106
107    /// The connector-provided partition values are invalid (missing/extra/duplicate keys,
108    /// or a value type does not match the schema column type).
109    #[error("Invalid partition values: {0}")]
110    InvalidPartitionValues(String),
111
112    /// A column was specified with a specific type, but it is not of that type
113    #[error("Expected column type: {0}")]
114    UnexpectedColumnType(String),
115
116    /// Data was expected, but not found
117    #[error("Expected is missing: {0}")]
118    MissingData(String),
119
120    /// A version for the delta table could not be found in the log
121    #[error("No table version found.")]
122    MissingVersion,
123
124    /// An error occurred while working with deletion vectors
125    #[error("Deletion Vector error: {0}")]
126    DeletionVector(String),
127
128    /// A selection vector is larger than data length
129    #[error("Selection vector is larger than data length: {0}")]
130    InvalidSelectionVector(String),
131
132    /// Transaction state is invalid for the requested operation
133    #[error("Invalid transaction state: {0}")]
134    InvalidTransactionState(String),
135
136    /// A specified URL was invalid
137    #[error("Invalid url: {0}")]
138    InvalidUrl(#[from] url::ParseError),
139
140    /// serde encountered malformed json
141    #[error(transparent)]
142    MalformedJson(serde_json::Error),
143
144    /// There was no metadata action in the delta log
145    #[error("No table metadata found in delta log.")]
146    MissingMetadata,
147
148    /// There was no protocol action in the delta log
149    #[error("No protocol found in delta log.")]
150    MissingProtocol,
151
152    /// Invalid protocol action was read from the log
153    #[error("Invalid protocol action in the delta log: {0}")]
154    InvalidProtocol(String),
155
156    /// Neither metadata nor protocol could be found in the delta log
157    #[error("No table metadata or protocol found in delta log.")]
158    MissingMetadataAndProtocol,
159
160    /// A string failed to parse as the specified data type
161    #[error("Failed to parse value '{0}' as '{1}'")]
162    ParseError(String, DataType),
163
164    /// A tokio executor failed to join a task
165    #[error("Join failure: {0}")]
166    JoinFailure(String),
167
168    /// Could not convert to string from utf-8
169    #[error("Could not convert to string from utf-8: {0}")]
170    Utf8Error(#[from] Utf8Error),
171
172    /// Could not parse an integer
173    #[error("Could not parse int: {0}")]
174    ParseIntError(#[from] ParseIntError),
175
176    #[error("Invalid column mapping mode: {0}")]
177    InvalidColumnMappingMode(String),
178
179    /// Asked for a table at an invalid location
180    #[error("Invalid table location: {0}.")]
181    InvalidTableLocation(String),
182
183    /// Precision or scale not compliant with delta specification
184    #[error("Invalid decimal: {0}")]
185    InvalidDecimal(String),
186
187    /// Inconsistent data passed to struct scalar
188    #[error("Invalid struct data: {0}")]
189    InvalidStructData(String),
190
191    /// Expressions did not parse or evaluate correctly
192    #[error("Invalid expression evaluation: {0}")]
193    InvalidExpressionEvaluation(String),
194
195    /// Unable to parse the name of a log path
196    #[error("Invalid log path: {0}")]
197    InvalidLogPath(String),
198
199    /// The file already exists at the path, prohibiting a non-overwrite write
200    #[error("File already exists: {0}")]
201    FileAlreadyExists(String),
202
203    /// Some functionality is currently unsupported
204    #[error("Unsupported: {0}")]
205    Unsupported(String),
206
207    /// Cannot write a version checksum (CRC) file for this snapshot
208    #[error("Checksum write unsupported: {0}")]
209    ChecksumWriteUnsupported(String),
210
211    /// Parsing error when attempting to deserialize an interval
212    #[error(transparent)]
213    ParseIntervalError(#[from] ParseIntervalError),
214
215    #[error("Change data feed is unsupported for the table at version {0}")]
216    ChangeDataFeedUnsupported(Version),
217
218    #[error("Change data feed encountered incompatible schema. Expected {0}, got {1}")]
219    ChangeDataFeedIncompatibleSchema(String, String),
220
221    /// Invalid checkpoint files
222    #[error("Invalid Checkpoint: {0}")]
223    InvalidCheckpoint(String),
224
225    /// Error while transforming a schema + leaves into an Expression of literals
226    #[error(transparent)]
227    LiteralExpressionTransformError(
228        #[from] crate::expressions::literal_expression_transform::Error,
229    ),
230
231    /// Schema mismatch has occurred or invalid schema used somewhere
232    #[error("Schema error: {0}")]
233    Schema(String),
234
235    /// Validation error for file statistics (e.g., missing required clustering column stats)
236    #[error("Stats validation error: {0}")]
237    StatsValidation(String),
238
239    /// Error during log history operations (timestamp queries, version lookups)
240    #[error(transparent)]
241    LogHistory(#[from] Box<crate::history_manager::error::LogHistoryError>),
242
243    #[cfg(feature = "declarative-plans")]
244    #[error("Declarative plan execution yielded the incorrect type: expected PlanResult::{expected}, got PlanResult::{actual}")]
245    PlanResultTypeMismatch {
246        expected: &'static str,
247        actual: &'static str,
248    },
249}
250
251// Convenience constructors for Error types that take a String argument
252impl Error {
253    pub(crate) fn checkpoint_write(msg: impl ToString) -> Self {
254        Self::CheckpointWrite(msg.to_string())
255    }
256
257    pub fn generic_err(source: impl Into<Box<dyn std::error::Error + Send + Sync>>) -> Self {
258        Self::GenericError {
259            source: source.into(),
260        }
261    }
262    pub fn generic(msg: impl ToString) -> Self {
263        Self::Generic(msg.to_string())
264    }
265    pub fn file_not_found(path: impl ToString) -> Self {
266        Self::FileNotFound(path.to_string())
267    }
268    pub fn missing_column(name: impl ToString) -> Self {
269        Self::MissingColumn(name.to_string()).with_backtrace()
270    }
271    pub fn unexpected_column_type(name: impl ToString) -> Self {
272        Self::UnexpectedColumnType(name.to_string())
273    }
274    pub fn invalid_partition_values(msg: impl ToString) -> Self {
275        Self::InvalidPartitionValues(msg.to_string())
276    }
277    pub fn missing_data(name: impl ToString) -> Self {
278        Self::MissingData(name.to_string())
279    }
280    pub fn deletion_vector(msg: impl ToString) -> Self {
281        Self::DeletionVector(msg.to_string())
282    }
283    pub fn engine_data_type(msg: impl ToString) -> Self {
284        Self::EngineDataType(msg.to_string())
285    }
286    pub fn join_failure(msg: impl ToString) -> Self {
287        Self::JoinFailure(msg.to_string())
288    }
289    pub fn invalid_table_location(location: impl ToString) -> Self {
290        Self::InvalidTableLocation(location.to_string())
291    }
292    pub fn invalid_column_mapping_mode(mode: impl ToString) -> Self {
293        Self::InvalidColumnMappingMode(mode.to_string())
294    }
295    pub fn invalid_decimal(msg: impl ToString) -> Self {
296        Self::InvalidDecimal(msg.to_string())
297    }
298    pub fn invalid_struct_data(msg: impl ToString) -> Self {
299        Self::InvalidStructData(msg.to_string())
300    }
301    pub fn invalid_expression(msg: impl ToString) -> Self {
302        Self::InvalidExpressionEvaluation(msg.to_string())
303    }
304    pub(crate) fn invalid_log_path(msg: impl ToString) -> Self {
305        Self::InvalidLogPath(msg.to_string())
306    }
307
308    pub fn internal_error(msg: impl ToString) -> Self {
309        Self::InternalError(msg.to_string()).with_backtrace()
310    }
311
312    pub fn invalid_protocol(msg: impl ToString) -> Self {
313        Self::InvalidProtocol(msg.to_string())
314    }
315
316    pub fn invalid_transaction_state(msg: impl ToString) -> Self {
317        Self::InvalidTransactionState(msg.to_string())
318    }
319
320    pub fn unsupported(msg: impl ToString) -> Self {
321        Self::Unsupported(msg.to_string())
322    }
323    pub fn change_data_feed_unsupported(version: impl Into<Version>) -> Self {
324        Self::ChangeDataFeedUnsupported(version.into())
325    }
326    pub(crate) fn change_data_feed_incompatible_schema(
327        expected: &StructType,
328        actual: &StructType,
329    ) -> Self {
330        Self::ChangeDataFeedIncompatibleSchema(format!("{expected:?}"), format!("{actual:?}"))
331    }
332
333    pub fn invalid_checkpoint(msg: impl ToString) -> Self {
334        Self::InvalidCheckpoint(msg.to_string())
335    }
336
337    pub fn schema(msg: impl ToString) -> Self {
338        Self::Schema(msg.to_string())
339    }
340
341    pub fn stats_validation(msg: impl ToString) -> Self {
342        Self::StatsValidation(msg.to_string())
343    }
344
345    #[cfg(feature = "declarative-plans")]
346    pub fn plan_result_type_mismatch(expected: &'static str, actual: &'static str) -> Self {
347        Self::PlanResultTypeMismatch { expected, actual }
348    }
349
350    // Capture a backtrace when the error is constructed.
351    #[must_use]
352    pub fn with_backtrace(self) -> Self {
353        let backtrace = Backtrace::capture();
354        match backtrace.status() {
355            BacktraceStatus::Captured => Self::Backtraced {
356                source: Box::new(self),
357                backtrace: Box::new(backtrace),
358            },
359            _ => self,
360        }
361    }
362}
363
364macro_rules! from_with_backtrace(
365    ( $(($error_type: ty, $error_variant: ident)), * ) => {
366        $(
367            impl From<$error_type> for Error {
368                fn from(value: $error_type) -> Self {
369                    Self::$error_variant(value).with_backtrace()
370                }
371            }
372        )*
373    };
374);
375
376from_with_backtrace!(
377    (serde_json::Error, MalformedJson),
378    (std::io::Error, IOError)
379);
380
381#[cfg(feature = "default-engine-base")]
382impl From<ArrowError> for Error {
383    fn from(value: ArrowError) -> Self {
384        Self::Arrow(value).with_backtrace()
385    }
386}
387
388#[cfg(feature = "default-engine-base")]
389impl From<object_store::Error> for Error {
390    fn from(value: object_store::Error) -> Self {
391        match value {
392            object_store::Error::NotFound { path, .. } => Self::file_not_found(path),
393            err => Self::ObjectStore(err),
394        }
395    }
396}
397
398/// This impl is needed so the `?` operator can auto-convert `Result<T, Infallible>` to
399/// `DeltaResult<T>`. For example, `TryFrom` impls for infallible conversions use `Infallible` as
400/// their error type, and this allows those results to be propagated with `?` in functions
401/// returning `DeltaResult`. The match is unreachable since `Infallible` has no variants.
402impl From<Infallible> for Error {
403    fn from(value: Infallible) -> Self {
404        match value {}
405    }
406}