use std::backtrace::{Backtrace, BacktraceStatus};
use std::convert::Infallible;
use std::num::ParseIntError;
use std::str::Utf8Error;
#[cfg(feature = "default-engine-base")]
use crate::arrow::error::ArrowError;
#[cfg(feature = "default-engine-base")]
use crate::object_store;
use crate::schema::{DataType, StructType};
use crate::table_properties::ParseIntervalError;
use crate::Version;
#[derive(Debug)]
pub struct ScalarConversionError {
expected: String,
actual: String,
path: Vec<String>,
}
impl ScalarConversionError {
pub(crate) fn new(expected: impl Into<String>, actual: impl Into<String>) -> Self {
Self {
expected: expected.into(),
actual: actual.into(),
path: Vec::new(),
}
}
fn add_path_context(mut self, element: impl Into<String>) -> Self {
self.path.push(element.into());
self
}
fn path_string(&self) -> String {
let mut path = String::new();
for element in self.path.iter().rev() {
if !path.is_empty() && !element.starts_with('[') {
path.push('.');
}
path.push_str(element);
}
path
}
}
impl std::fmt::Display for ScalarConversionError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut target = self.path_string();
if target.is_empty() {
target.push_str("scalar");
}
write!(
f,
"Cannot convert {target}: expected {}, found {}",
self.expected, self.actual
)
}
}
impl std::error::Error for ScalarConversionError {}
pub(crate) fn add_scalar_path_context(error: Error, element: impl Into<String>) -> Error {
match error {
Error::ScalarConversion(error) => Error::ScalarConversion(error.add_path_context(element)),
other => other,
}
}
pub type DeltaResult<T, E = Error> = std::result::Result<T, E>;
pub type DeltaResultIterator<'a, T> = Box<dyn Iterator<Item = DeltaResult<T>> + Send + 'a>;
pub type DeltaResultIteratorStatic<T> = DeltaResultIterator<'static, T>;
#[non_exhaustive]
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("{source}\n{backtrace}")]
Backtraced {
source: Box<Self>,
backtrace: Box<Backtrace>,
},
#[cfg(feature = "default-engine-base")]
#[error(transparent)]
Arrow(ArrowError),
#[error("Error writing checkpoint: {0}")]
CheckpointWrite(String),
#[error("Invalid engine data type. Could not convert to {0}")]
EngineDataType(String),
#[error("Error extracting type {0}: {1}")]
Extract(&'static str, &'static str),
#[error(transparent)]
ScalarConversion(#[from] ScalarConversionError),
#[error("Generic delta kernel error: {0}")]
Generic(String),
#[error("Generic error: {source}")]
GenericError {
source: Box<dyn std::error::Error + Send + Sync + 'static>,
},
#[error("Max catalog version error: {0}")]
MaxCatalogVersion(String),
#[error("Log tail versions {first_version} and {second_version} are not contiguous")]
LogTailVersionsNotContiguous {
first_version: Version,
second_version: Version,
},
#[error(transparent)]
IOError(std::io::Error),
#[error("Internal error {0}. This is a kernel bug, please report.")]
InternalError(String),
#[cfg(feature = "default-engine-base")]
#[error("Arrow error: {0}")]
Parquet(#[from] crate::parquet::errors::ParquetError),
#[cfg(feature = "default-engine-base")]
#[error("Error interacting with object store: {0}")]
ObjectStore(object_store::Error),
#[cfg(feature = "default-engine-base")]
#[error("Object store path error: {0}")]
ObjectStorePath(#[from] object_store::path::Error),
#[cfg(feature = "default-engine-base")]
#[error("Reqwest Error: {0}")]
Reqwest(#[from] reqwest::Error),
#[error("File not found: {0}")]
FileNotFound(String),
#[error("{0}")]
MissingColumn(String),
#[error("Invalid partition values: {0}")]
InvalidPartitionValues(String),
#[error("Expected column type: {0}")]
UnexpectedColumnType(String),
#[error("Expected is missing: {0}")]
MissingData(String),
#[error("No table version found.")]
MissingVersion,
#[error("Deletion Vector error: {0}")]
DeletionVector(String),
#[error("Selection vector is larger than data length: {0}")]
InvalidSelectionVector(String),
#[error("Invalid transaction state: {0}")]
InvalidTransactionState(String),
#[error("Invalid url: {0}")]
InvalidUrl(#[from] url::ParseError),
#[error(transparent)]
MalformedJson(serde_json::Error),
#[error("No table metadata found in delta log.")]
MissingMetadata,
#[error("No protocol found in delta log.")]
MissingProtocol,
#[error("Invalid protocol action in the delta log: {0}")]
InvalidProtocol(String),
#[error("No table metadata or protocol found in delta log.")]
MissingMetadataAndProtocol,
#[error("Failed to parse value '{0}' as '{1}'")]
ParseError(String, DataType),
#[error("Join failure: {0}")]
JoinFailure(String),
#[error("Could not convert to string from utf-8: {0}")]
Utf8Error(#[from] Utf8Error),
#[error("Could not parse int: {0}")]
ParseIntError(#[from] ParseIntError),
#[error("Invalid column mapping mode: {0}")]
InvalidColumnMappingMode(String),
#[error("Invalid table location: {0}.")]
InvalidTableLocation(String),
#[error("Invalid decimal: {0}")]
InvalidDecimal(String),
#[error("Invalid geo parameters: {0}")]
InvalidGeoParams(String),
#[error("Invalid struct data: {0}")]
InvalidStructData(String),
#[error("Invalid expression evaluation: {0}")]
InvalidExpressionEvaluation(String),
#[error("Invalid log path: {0}")]
InvalidLogPath(String),
#[error("File already exists: {0}")]
FileAlreadyExists(String),
#[error("Unsupported: {0}")]
Unsupported(String),
#[error("Checksum write unsupported: {0}")]
ChecksumWriteUnsupported(String),
#[error(transparent)]
ParseIntervalError(#[from] ParseIntervalError),
#[error("Change data feed is unsupported for the table at version {0}")]
ChangeDataFeedUnsupported(Version),
#[error(
"Row tracking (delta.enableRowTracking) must be enabled for the entire row-tracking change \
feed range, but it is not enabled at version {0}"
)]
RowTrackingChangeFeedUnsupported(Version),
#[error("Change data feed encountered incompatible schema. Expected {0}, got {1}")]
ChangeDataFeedIncompatibleSchema(String, String),
#[error("Invalid Checkpoint: {0}")]
InvalidCheckpoint(String),
#[error(transparent)]
LiteralExpressionTransformError(
#[from] crate::expressions::literal_expression_transform::Error,
),
#[error("Schema error: {0}")]
Schema(String),
#[error("Stats validation error: {0}")]
StatsValidation(String),
#[error(transparent)]
LogHistory(#[from] Box<crate::history_manager::error::LogHistoryError>),
#[cfg(feature = "declarative-plans")]
#[error("Declarative plan execution yielded the incorrect type: expected PlanResult::{expected}, got PlanResult::{actual}")]
PlanResultTypeMismatch {
expected: &'static str,
actual: &'static str,
},
#[error("Operation cancelled")]
Cancelled,
}
impl Error {
pub(crate) fn scalar_conversion(
expected: impl Into<String>,
actual: impl Into<String>,
) -> Self {
ScalarConversionError::new(expected, actual).into()
}
pub(crate) fn checkpoint_write(msg: impl ToString) -> Self {
Self::CheckpointWrite(msg.to_string())
}
pub fn generic_err(source: impl Into<Box<dyn std::error::Error + Send + Sync>>) -> Self {
Self::GenericError {
source: source.into(),
}
}
pub fn generic(msg: impl ToString) -> Self {
Self::Generic(msg.to_string())
}
pub fn file_not_found(path: impl ToString) -> Self {
Self::FileNotFound(path.to_string())
}
pub fn missing_column(name: impl ToString) -> Self {
Self::MissingColumn(name.to_string()).with_backtrace()
}
pub fn unexpected_column_type(name: impl ToString) -> Self {
Self::UnexpectedColumnType(name.to_string())
}
pub fn invalid_partition_values(msg: impl ToString) -> Self {
Self::InvalidPartitionValues(msg.to_string())
}
pub fn missing_data(name: impl ToString) -> Self {
Self::MissingData(name.to_string())
}
pub fn deletion_vector(msg: impl ToString) -> Self {
Self::DeletionVector(msg.to_string())
}
pub fn engine_data_type(msg: impl ToString) -> Self {
Self::EngineDataType(msg.to_string())
}
pub fn join_failure(msg: impl ToString) -> Self {
Self::JoinFailure(msg.to_string())
}
pub fn invalid_table_location(location: impl ToString) -> Self {
Self::InvalidTableLocation(location.to_string())
}
pub fn invalid_column_mapping_mode(mode: impl ToString) -> Self {
Self::InvalidColumnMappingMode(mode.to_string())
}
pub fn invalid_decimal(msg: impl ToString) -> Self {
Self::InvalidDecimal(msg.to_string())
}
#[cfg(feature = "geo-type-in-dev")]
pub fn invalid_geo_params(msg: impl ToString) -> Self {
Self::InvalidGeoParams(msg.to_string())
}
pub fn invalid_struct_data(msg: impl ToString) -> Self {
Self::InvalidStructData(msg.to_string())
}
pub fn invalid_expression(msg: impl ToString) -> Self {
Self::InvalidExpressionEvaluation(msg.to_string())
}
pub(crate) fn invalid_log_path(msg: impl ToString) -> Self {
Self::InvalidLogPath(msg.to_string())
}
pub fn internal_error(msg: impl ToString) -> Self {
Self::InternalError(msg.to_string()).with_backtrace()
}
pub fn invalid_protocol(msg: impl ToString) -> Self {
Self::InvalidProtocol(msg.to_string())
}
pub fn invalid_transaction_state(msg: impl ToString) -> Self {
Self::InvalidTransactionState(msg.to_string())
}
pub fn unsupported(msg: impl ToString) -> Self {
Self::Unsupported(msg.to_string())
}
pub fn change_data_feed_unsupported(version: impl Into<Version>) -> Self {
Self::ChangeDataFeedUnsupported(version.into())
}
pub(crate) fn row_tracking_change_feed_unsupported(version: impl Into<Version>) -> Self {
Self::RowTrackingChangeFeedUnsupported(version.into())
}
pub(crate) fn change_data_feed_incompatible_schema(
expected: &StructType,
actual: &StructType,
) -> Self {
Self::ChangeDataFeedIncompatibleSchema(expected.to_string(), actual.to_string())
}
pub(crate) fn change_data_feed_incompatible_schema_at_version(
expected: &StructType,
actual: &StructType,
version: Version,
) -> Self {
Self::ChangeDataFeedIncompatibleSchema(
expected.to_string(),
format!("schema at version {version}: {actual}"),
)
}
pub fn invalid_checkpoint(msg: impl ToString) -> Self {
Self::InvalidCheckpoint(msg.to_string())
}
pub fn schema(msg: impl ToString) -> Self {
Self::Schema(msg.to_string())
}
pub fn stats_validation(msg: impl ToString) -> Self {
Self::StatsValidation(msg.to_string())
}
#[cfg(feature = "declarative-plans")]
pub fn plan_result_type_mismatch(expected: &'static str, actual: &'static str) -> Self {
Self::PlanResultTypeMismatch { expected, actual }
}
#[must_use]
pub fn with_backtrace(self) -> Self {
let backtrace = Backtrace::capture();
match backtrace.status() {
BacktraceStatus::Captured => Self::Backtraced {
source: Box::new(self),
backtrace: Box::new(backtrace),
},
_ => self,
}
}
}
macro_rules! from_with_backtrace(
( $(($error_type: ty, $error_variant: ident)), * ) => {
$(
impl From<$error_type> for Error {
fn from(value: $error_type) -> Self {
Self::$error_variant(value).with_backtrace()
}
}
)*
};
);
from_with_backtrace!(
(serde_json::Error, MalformedJson),
(std::io::Error, IOError)
);
#[cfg(feature = "default-engine-base")]
impl From<ArrowError> for Error {
fn from(value: ArrowError) -> Self {
Self::Arrow(value).with_backtrace()
}
}
#[cfg(feature = "default-engine-base")]
impl From<object_store::Error> for Error {
fn from(value: object_store::Error) -> Self {
match value {
object_store::Error::NotFound { path, .. } => Self::file_not_found(path),
err => Self::ObjectStore(err),
}
}
}
impl From<Infallible> for Error {
fn from(value: Infallible) -> Self {
match value {}
}
}