Skip to main content

datafusion_ducklake/
error.rs

1//! Error types for the DuckLake DataFusion extension
2
3use std::fmt;
4
5use thiserror::Error;
6
7/// The data-write mode that attempted an unsupported column type change.
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum TypeChangeWriteMode {
10    /// Drop existing data and replace with new data.
11    Replace,
12    /// Keep existing data and append new records.
13    Append,
14}
15
16impl fmt::Display for TypeChangeWriteMode {
17    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
18        match self {
19            TypeChangeWriteMode::Replace => write!(f, "Replace"),
20            TypeChangeWriteMode::Append => write!(f, "Append"),
21        }
22    }
23}
24
25/// The operation that attempted an unsupported column type change.
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub enum TypeChangeOperation {
28    /// Explicit metadata-only schema evolution through `promote_column_type`.
29    PromoteColumnType,
30    /// A data write tried to change the type of an existing same-name column.
31    DataWrite {
32        mode: TypeChangeWriteMode,
33    },
34}
35
36impl fmt::Display for TypeChangeOperation {
37    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
38        match self {
39            TypeChangeOperation::PromoteColumnType => write!(f, "promote_column_type"),
40            TypeChangeOperation::DataWrite {
41                mode,
42            } => write!(f, "{mode} data write"),
43        }
44    }
45}
46
47/// Error type for DuckLake operations
48#[derive(Error, Debug)]
49pub enum DuckLakeError {
50    /// Error from DataFusion
51    #[error("DataFusion error: {0}")]
52    DataFusion(#[from] datafusion::error::DataFusionError),
53
54    /// Error from Arrow
55    #[error("Arrow error: {0}")]
56    Arrow(#[from] arrow::error::ArrowError),
57
58    /// DuckDB error
59    #[cfg(feature = "metadata-duckdb")]
60    #[error("DuckDB error: {0}")]
61    DuckDb(#[from] duckdb::Error),
62
63    /// sqlx database error (for PostgreSQL/MySQL/SQLite metadata providers)
64    #[cfg(any(
65        feature = "metadata-postgres",
66        feature = "metadata-mysql",
67        feature = "metadata-sqlite"
68    ))]
69    #[error("Database error: {0}")]
70    Sqlx(#[from] sqlx::Error),
71
72    /// Catalog not found
73    #[error("Catalog not found: {0}")]
74    CatalogNotFound(String),
75
76    /// Schema not found
77    #[error("Schema not found: {0}")]
78    SchemaNotFound(String),
79
80    /// Table not found
81    #[error("Table not found: {0}")]
82    TableNotFound(String),
83
84    /// Invalid snapshot
85    #[error("Invalid snapshot: {0}")]
86    InvalidSnapshot(String),
87
88    /// Invalid catalog configuration
89    #[error("Invalid configuration: {0}")]
90    InvalidConfig(String),
91
92    /// A write or promotion tried to change an existing column to a type that
93    /// DuckLake cannot adopt through metadata-only schema evolution.
94    #[error(
95        "Unsupported type change during {operation}: column '{column}' from '{from}' to '{to}'"
96    )]
97    UnsupportedTypeChange {
98        operation: TypeChangeOperation,
99        column: String,
100        from: String,
101        to: String,
102    },
103
104    /// Unsupported DuckLake type
105    #[error("Unsupported DuckLake type: {0}")]
106    UnsupportedType(String),
107
108    /// Unsupported feature
109    #[error("Unsupported feature: {0}")]
110    Unsupported(String),
111
112    /// ObjectStore error
113    #[error("ObjectStore error: {0}")]
114    ObjectStore(#[from] object_store::Error),
115
116    /// IO error
117    #[error("IO error: {0}")]
118    Io(#[from] std::io::Error),
119
120    /// Parquet error
121    #[error("Parquet error: {0}")]
122    Parquet(#[from] parquet::errors::ParquetError),
123
124    /// A concurrent write conflict detected at commit time: another writer
125    /// published a newer generation of the table since this write began. The
126    /// loser aborts (DuckLake-style optimistic concurrency) rather than silently
127    /// unioning or clobbering the concurrent commit. Callers may retry.
128    #[error("Write conflict: {0}")]
129    Conflict(String),
130
131    /// Generic error
132    #[error("Internal error: {0}")]
133    Internal(String),
134}
135
136impl From<DuckLakeError> for datafusion::error::DataFusionError {
137    fn from(err: DuckLakeError) -> Self {
138        match err {
139            // If it's already a DataFusion error, unwrap it
140            DuckLakeError::DataFusion(e) => e,
141            // For all other errors, wrap them as External
142            other => datafusion::error::DataFusionError::External(Box::new(other)),
143        }
144    }
145}