better_duck_core/error.rs
1#![allow(dead_code)]
2// Direct copy from DuckDB
3
4use crate::ffi::{duckdb_type, Error as FFIError};
5use std::{error, fmt, path::PathBuf, result, str};
6
7/// Describes why a value conversion from or to DuckDB failed.
8#[derive(Debug)]
9pub enum DuckDBConversionError {
10 /// The DuckDB column type did not match the expected Rust type.
11 TypeMismatch {
12 /// The type that was expected.
13 expected: duckdb_type,
14 /// The type that was actually found.
15 found: duckdb_type,
16 },
17 /// A general conversion error with a description.
18 ConversionError(String),
19 /// A null value was encountered where a non-null value was required.
20 NullValue,
21 /// The conversion would lose precision (e.g. Decimal scale overflow).
22 PrecisionLoss(String),
23}
24
25/// Enum listing possible errors from duckdb.
26#[derive(Debug)]
27#[allow(clippy::enum_variant_names)]
28#[non_exhaustive]
29pub enum Error {
30 /// An error from an underlying DuckDB call.
31 DuckDBFailure(FFIError, Option<String>),
32
33 /// Error when the value of a particular column is requested, but it cannot
34 /// be converted to the requested Rust type.
35 // FromSqlConversionFailure(usize, Type, Box<dyn error::Error + Send + Sync + 'static>),
36
37 /// Error when DuckDB gives us an integral value outside the range of the
38 /// requested type (e.g., trying to get the value 1000 into a `u8`).
39 /// The associated `usize` is the column index,
40 /// and the associated `i64` is the value returned by DuckDB.
41 IntegralValueOutOfRange(usize, i128),
42
43 /// Error converting a string to UTF-8.
44 Utf8Error(str::Utf8Error),
45
46 /// Error converting a string to a C-compatible string because it contained
47 /// an embedded nul.
48 NulError(::std::ffi::NulError),
49
50 /// Error when using SQL named parameters and passing a parameter name not
51 /// present in the SQL.
52 InvalidParameterName(String),
53
54 /// Error converting a file path to a string.
55 InvalidPath(PathBuf),
56
57 /// Error returned when an [`execute`](crate::connection::Connection::execute) call
58 /// returns rows.
59 ExecuteReturnedResults,
60
61 /// Error when a query that was expected to return at least one row did not
62 /// return any.
63 QueryReturnedNoRows,
64
65 /// Error when the value of a particular column is requested, but the index
66 /// is out of range for the statement.
67 InvalidColumnIndex(usize),
68
69 /// Error when the value of a named column is requested, but no column
70 /// matches the name for the statement.
71 InvalidColumnName(String),
72
73 /// Error when the value of a particular column is requested, but the type
74 /// of the result in that column cannot be converted to the requested
75 /// Rust type.
76 // InvalidColumnType(usize, String, Type),
77
78 /// Error when a query that was expected to insert one row did not insert
79 /// any or inserted many.
80 StatementChangedRows(usize),
81
82 /// Error available for the implementors of the
83 /// [`AppendAble`](crate::types::appendable::AppendAble) trait.
84 ToSqlConversionFailure(Box<dyn error::Error + Send + Sync + 'static>),
85
86 /// Error when the SQL is not a `SELECT`, is not read-only.
87 InvalidQuery,
88
89 /// Error when the SQL contains multiple statements.
90 MultipleStatement,
91
92 /// Error when the number of bound parameters does not match the number of
93 /// parameters in the query. The first `usize` is how many parameters were
94 /// given, the 2nd is how many were expected.
95 InvalidParameterCount(usize, usize),
96
97 /// An error occurred while appending a value via the DuckDB appender API.
98 AppendError,
99
100 /// A value conversion error.
101 ConversionError(DuckDBConversionError),
102
103 /// An unexpected error with no more specific classification.
104 #[allow(non_camel_case_types)]
105 UNKNOWN(Box<dyn ::std::error::Error + Send + Sync + 'static>),
106
107 /// A background blocking task panicked or was cancelled before it produced a result.
108 BackgroundTaskFailed(String),
109
110 /// A connection pool operation failed (checkout timeout, manager error).
111 Pool(String),
112}
113
114/// A typedef of the result returned by many methods.
115pub type Result<T, E = Error> = result::Result<T, E>;
116
117impl PartialEq for Error {
118 fn eq(
119 &self,
120 other: &Error,
121 ) -> bool {
122 match (self, other) {
123 (Error::DuckDBFailure(e1, s1), Error::DuckDBFailure(e2, s2)) => e1 == e2 && s1 == s2,
124 (Error::IntegralValueOutOfRange(i1, n1), Error::IntegralValueOutOfRange(i2, n2)) => {
125 i1 == i2 && n1 == n2
126 },
127 (Error::Utf8Error(e1), Error::Utf8Error(e2)) => e1 == e2,
128 (Error::NulError(e1), Error::NulError(e2)) => e1 == e2,
129 (Error::InvalidParameterName(n1), Error::InvalidParameterName(n2)) => n1 == n2,
130 (Error::InvalidPath(p1), Error::InvalidPath(p2)) => p1 == p2,
131 (Error::ExecuteReturnedResults, Error::ExecuteReturnedResults) => true,
132 (Error::QueryReturnedNoRows, Error::QueryReturnedNoRows) => true,
133 (Error::InvalidColumnIndex(i1), Error::InvalidColumnIndex(i2)) => i1 == i2,
134 (Error::InvalidColumnName(n1), Error::InvalidColumnName(n2)) => n1 == n2,
135 // (Error::InvalidColumnType(i1, n1, t1), Error::InvalidColumnType(i2, n2, t2)) => {
136 // i1 == i2 && t1 == t2 && n1 == n2
137 // }
138 (Error::StatementChangedRows(n1), Error::StatementChangedRows(n2)) => n1 == n2,
139 (Error::InvalidParameterCount(i1, n1), Error::InvalidParameterCount(i2, n2)) => {
140 i1 == i2 && n1 == n2
141 },
142 (..) => false,
143 }
144 }
145}
146
147impl From<str::Utf8Error> for Error {
148 #[cold]
149 fn from(err: str::Utf8Error) -> Error {
150 Error::Utf8Error(err)
151 }
152}
153
154impl From<::std::ffi::NulError> for Error {
155 #[cold]
156 fn from(err: ::std::ffi::NulError) -> Error {
157 Error::NulError(err)
158 }
159}
160
161const UNKNOWN_COLUMN: usize = usize::MAX;
162
163/// The conversion isn't precise, but it's convenient to have it
164/// to allow use of `get_raw(…).as_…()?` in callbacks that take `Error`.
165/// ```rust,ignore
166/// impl From<FromSqlError> for Error {
167/// #[cold]
168/// fn from(err: FromSqlError) -> Error {
169/// // The error type requires index and type fields, but they aren't known in this
170/// // context.
171/// match err {
172/// FromSqlError::OutOfRange(val) => Error::IntegralValueOutOfRange(UNKNOWN_COLUMN, val),
173/// #[cfg(feature = "uuid")]
174/// FromSqlError::InvalidUuidSize(_) => {
175/// Error::FromSqlConversionFailure(UNKNOWN_COLUMN, Type::Blob, Box::new(err))
176/// }
177/// FromSqlError::Other(source) => {
178/// Error::FromSqlConversionFailure(UNKNOWN_COLUMN, Type::Null, source)
179/// }
180/// _ => Error::FromSqlConversionFailure(UNKNOWN_COLUMN, Type::Null, Box::new(err)),
181/// }
182/// }
183/// }
184/// ```
185///
186impl fmt::Display for Error {
187 fn fmt(
188 &self,
189 f: &mut fmt::Formatter<'_>,
190 ) -> fmt::Result {
191 match self {
192 Error::DuckDBFailure(ref err, None) => err.fmt(f),
193 Error::DuckDBFailure(_, Some(ref s)) => write!(f, "{s}"),
194 // Error::FromSqlConversionFailure(i, ref t, ref err) => {
195 // if i != UNKNOWN_COLUMN {
196 // write!(f, "Conversion error from type {t} at index: {i}, {err}")
197 // } else {
198 // err.fmt(f)
199 // }
200 // }
201 Error::IntegralValueOutOfRange(col, val) => {
202 if *col != UNKNOWN_COLUMN {
203 write!(f, "Integer {val} out of range at index {col}")
204 } else {
205 write!(f, "Integer {val} out of range")
206 }
207 },
208 Error::Utf8Error(ref err) => err.fmt(f),
209 Error::NulError(ref err) => err.fmt(f),
210 Error::InvalidParameterName(ref name) => write!(f, "Invalid parameter name: {name}"),
211 Error::InvalidPath(ref p) => write!(f, "Invalid path: {}", p.to_string_lossy()),
212 Error::ExecuteReturnedResults => {
213 write!(f, "Execute returned results - did you mean to call query?")
214 },
215 Error::QueryReturnedNoRows => write!(f, "Query returned no rows"),
216 Error::InvalidColumnIndex(i) => write!(f, "Invalid column index: {i}"),
217 Error::InvalidColumnName(ref name) => write!(f, "Invalid column name: {name}"),
218 // Error::InvalidColumnType(i, ref name, ref t) => {
219 // write!(f, "Invalid column type {t} at index: {i}, name: {name}")
220 // }
221 // Error::ArrowTypeToDuckdbType(ref name, ref t) => {
222 // write!(f, "Invalid column type {t} , name: {name}")
223 // }
224 Error::InvalidParameterCount(i1, n1) => {
225 write!(f, "Wrong number of parameters passed to query. Got {i1}, needed {n1}")
226 },
227 Error::StatementChangedRows(i) => write!(f, "Query changed {i} rows"),
228 Error::ToSqlConversionFailure(ref err) => err.fmt(f),
229 Error::InvalidQuery => write!(f, "Query is not read-only"),
230 Error::MultipleStatement => write!(f, "Multiple statements provided"),
231 Error::AppendError => write!(f, "Append error"),
232 Error::ConversionError(ref err) => match err {
233 DuckDBConversionError::TypeMismatch { expected, found } => {
234 write!(f, "Type mismatch: expected {expected}, found {found}")
235 },
236 DuckDBConversionError::ConversionError(ref msg) => {
237 write!(f, "Conversion error: {msg}")
238 },
239 DuckDBConversionError::NullValue => write!(f, "Null value encountered"),
240 DuckDBConversionError::PrecisionLoss(ref msg) => write!(f, "Precision loss: {msg}"),
241 },
242 Error::UNKNOWN(e) => write!(f, "Unknown error: {e}"),
243 Error::BackgroundTaskFailed(ref msg) => write!(f, "Background task failed: {msg}"),
244 Error::Pool(ref msg) => write!(f, "Connection pool error: {msg}"),
245 }
246 }
247}
248
249impl error::Error for Error {
250 fn source(&self) -> Option<&(dyn error::Error + 'static)> {
251 match self {
252 Error::DuckDBFailure(ref err, _) => Some(err),
253 Error::Utf8Error(ref err) => Some(err),
254 Error::NulError(ref err) => Some(err),
255
256 Error::IntegralValueOutOfRange(..)
257 | Error::InvalidParameterName(_)
258 | Error::ExecuteReturnedResults
259 | Error::QueryReturnedNoRows
260 | Error::InvalidColumnIndex(_)
261 | Error::InvalidColumnName(_)
262 // | Error::InvalidColumnType(..)
263 | Error::InvalidPath(_)
264 | Error::InvalidParameterCount(..)
265 | Error::StatementChangedRows(_)
266 | Error::InvalidQuery
267 | Error::AppendError
268 // | Error::ArrowTypeToDuckdbType(..)
269 | Error::MultipleStatement
270 | Error::ConversionError(_) => None,
271 // Error::FromSqlConversionFailure(_, _, ref err)
272 Error::ToSqlConversionFailure(ref err) => Some(&**err),
273 Error::UNKNOWN(e) => Some(e.as_ref()),
274 Error::BackgroundTaskFailed(_) | Error::Pool(_) => None,
275 }
276 }
277}