1use arrow::datatypes::DataType;
2
3use super::Result;
4use crate::{
5 ffi,
6 types::{FromSqlError, Type},
7};
8use std::{error, ffi::CStr, fmt, path::PathBuf, str};
9
10#[derive(Debug)]
12#[allow(clippy::enum_variant_names)]
13#[non_exhaustive]
14pub enum Error {
15 DuckDBFailure(ffi::Error, Option<String>),
17
18 FromSqlConversionFailure(usize, Type, Box<dyn error::Error + Send + Sync + 'static>),
21
22 IntegralValueOutOfRange(usize, i128),
27
28 Utf8Error(str::Utf8Error),
30
31 NulError(::std::ffi::NulError),
34
35 InvalidParameterName(String),
38
39 InvalidPath(PathBuf),
41
42 ExecuteReturnedResults,
45
46 QueryReturnedNoRows,
49
50 InvalidColumnIndex(usize),
53
54 InvalidColumnName(String),
57
58 InvalidColumnType(usize, String, Type),
62
63 ArrowTypeToDuckdbType(String, DataType),
65
66 StatementChangedRows(usize),
69
70 ToSqlConversionFailure(Box<dyn error::Error + Send + Sync + 'static>),
73
74 InvalidQuery,
76
77 MultipleStatement,
79 InvalidParameterCount(usize, usize),
83
84 AppendError,
86}
87
88impl PartialEq for Error {
89 fn eq(&self, other: &Error) -> bool {
90 match (self, other) {
91 (Error::DuckDBFailure(e1, s1), Error::DuckDBFailure(e2, s2)) => e1 == e2 && s1 == s2,
92 (Error::IntegralValueOutOfRange(i1, n1), Error::IntegralValueOutOfRange(i2, n2)) => i1 == i2 && n1 == n2,
93 (Error::Utf8Error(e1), Error::Utf8Error(e2)) => e1 == e2,
94 (Error::NulError(e1), Error::NulError(e2)) => e1 == e2,
95 (Error::InvalidParameterName(n1), Error::InvalidParameterName(n2)) => n1 == n2,
96 (Error::InvalidPath(p1), Error::InvalidPath(p2)) => p1 == p2,
97 (Error::ExecuteReturnedResults, Error::ExecuteReturnedResults) => true,
98 (Error::QueryReturnedNoRows, Error::QueryReturnedNoRows) => true,
99 (Error::InvalidColumnIndex(i1), Error::InvalidColumnIndex(i2)) => i1 == i2,
100 (Error::InvalidColumnName(n1), Error::InvalidColumnName(n2)) => n1 == n2,
101 (Error::InvalidColumnType(i1, n1, t1), Error::InvalidColumnType(i2, n2, t2)) => {
102 i1 == i2 && t1 == t2 && n1 == n2
103 }
104 (Error::StatementChangedRows(n1), Error::StatementChangedRows(n2)) => n1 == n2,
105 (Error::InvalidParameterCount(i1, n1), Error::InvalidParameterCount(i2, n2)) => i1 == i2 && n1 == n2,
106 (..) => false,
107 }
108 }
109}
110
111impl From<str::Utf8Error> for Error {
112 #[cold]
113 fn from(err: str::Utf8Error) -> Error {
114 Error::Utf8Error(err)
115 }
116}
117
118impl From<::std::ffi::NulError> for Error {
119 #[cold]
120 fn from(err: ::std::ffi::NulError) -> Error {
121 Error::NulError(err)
122 }
123}
124
125const UNKNOWN_COLUMN: usize = usize::MAX;
126
127impl From<FromSqlError> for Error {
130 #[cold]
131 fn from(err: FromSqlError) -> Error {
132 match err {
135 FromSqlError::OutOfRange(val) => Error::IntegralValueOutOfRange(UNKNOWN_COLUMN, val),
136 #[cfg(feature = "uuid")]
137 FromSqlError::InvalidUuidSize(_) => {
138 Error::FromSqlConversionFailure(UNKNOWN_COLUMN, Type::Blob, Box::new(err))
139 }
140 FromSqlError::Other(source) => Error::FromSqlConversionFailure(UNKNOWN_COLUMN, Type::Null, source),
141 _ => Error::FromSqlConversionFailure(UNKNOWN_COLUMN, Type::Null, Box::new(err)),
142 }
143 }
144}
145
146impl fmt::Display for Error {
147 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
148 match *self {
149 Error::DuckDBFailure(ref err, None) => err.fmt(f),
150 Error::DuckDBFailure(_, Some(ref s)) => write!(f, "{s}"),
151 Error::FromSqlConversionFailure(i, ref t, ref err) => {
152 if i != UNKNOWN_COLUMN {
153 write!(f, "Conversion error from type {t} at index: {i}, {err}")
154 } else {
155 err.fmt(f)
156 }
157 }
158 Error::IntegralValueOutOfRange(col, val) => {
159 if col != UNKNOWN_COLUMN {
160 write!(f, "Integer {val} out of range at index {col}")
161 } else {
162 write!(f, "Integer {val} out of range")
163 }
164 }
165 Error::Utf8Error(ref err) => err.fmt(f),
166 Error::NulError(ref err) => err.fmt(f),
167 Error::InvalidParameterName(ref name) => write!(f, "Invalid parameter name: {name}"),
168 Error::InvalidPath(ref p) => write!(f, "Invalid path: {}", p.to_string_lossy()),
169 Error::ExecuteReturnedResults => {
170 write!(f, "Execute returned results - did you mean to call query?")
171 }
172 Error::QueryReturnedNoRows => write!(f, "Query returned no rows"),
173 Error::InvalidColumnIndex(i) => write!(f, "Invalid column index: {i}"),
174 Error::InvalidColumnName(ref name) => write!(f, "Invalid column name: {name}"),
175 Error::InvalidColumnType(i, ref name, ref t) => {
176 write!(f, "Invalid column type {t} at index: {i}, name: {name}")
177 }
178 Error::ArrowTypeToDuckdbType(ref name, ref t) => {
179 write!(f, "Invalid column type {t} , name: {name}")
180 }
181 Error::InvalidParameterCount(i1, n1) => {
182 write!(f, "Wrong number of parameters passed to query. Got {i1}, needed {n1}")
183 }
184 Error::StatementChangedRows(i) => write!(f, "Query changed {i} rows"),
185 Error::ToSqlConversionFailure(ref err) => err.fmt(f),
186 Error::InvalidQuery => write!(f, "Query is not read-only"),
187 Error::MultipleStatement => write!(f, "Multiple statements provided"),
188 Error::AppendError => write!(f, "Append error"),
189 }
190 }
191}
192
193impl error::Error for Error {
194 fn source(&self) -> Option<&(dyn error::Error + 'static)> {
195 match *self {
196 Error::DuckDBFailure(ref err, _) => Some(err),
197 Error::Utf8Error(ref err) => Some(err),
198 Error::NulError(ref err) => Some(err),
199
200 Error::IntegralValueOutOfRange(..)
201 | Error::InvalidParameterName(_)
202 | Error::ExecuteReturnedResults
203 | Error::QueryReturnedNoRows
204 | Error::InvalidColumnIndex(_)
205 | Error::InvalidColumnName(_)
206 | Error::InvalidColumnType(..)
207 | Error::InvalidPath(_)
208 | Error::InvalidParameterCount(..)
209 | Error::StatementChangedRows(_)
210 | Error::InvalidQuery
211 | Error::AppendError
212 | Error::ArrowTypeToDuckdbType(..)
213 | Error::MultipleStatement => None,
214 Error::FromSqlConversionFailure(_, _, ref err) | Error::ToSqlConversionFailure(ref err) => Some(&**err),
215 }
216 }
217}
218
219#[inline]
222pub(crate) fn error_from_duckdb_code(code: ffi::duckdb_state, message: Option<String>) -> Result<()> {
223 Err(Error::DuckDBFailure(ffi::Error::new(code), message))
224}
225
226#[cold]
227#[inline]
228pub fn result_from_duckdb_appender(code: ffi::duckdb_state, appender: *mut ffi::duckdb_appender) -> Result<()> {
229 if code == ffi::DuckDBSuccess {
230 return Ok(());
231 }
232 unsafe {
233 let message = if (*appender).is_null() {
234 Some("appender is null".to_string())
235 } else {
236 let c_err = ffi::duckdb_appender_error(*appender);
237 let message = Some(CStr::from_ptr(c_err).to_string_lossy().to_string());
238 ffi::duckdb_appender_destroy(appender);
239 message
240 };
241 error_from_duckdb_code(code, message)
242 }
243}
244
245#[cold]
246#[inline]
247pub fn result_from_duckdb_prepare(code: ffi::duckdb_state, mut prepare: ffi::duckdb_prepared_statement) -> Result<()> {
248 if code == ffi::DuckDBSuccess {
249 return Ok(());
250 }
251 unsafe {
252 let message = if prepare.is_null() {
253 Some("prepare is null".to_string())
254 } else {
255 let c_err = ffi::duckdb_prepare_error(prepare);
256 let message = Some(CStr::from_ptr(c_err).to_string_lossy().to_string());
257 ffi::duckdb_destroy_prepare(&mut prepare);
258 message
259 };
260 error_from_duckdb_code(code, message)
261 }
262}
263
264#[cold]
265#[inline]
266pub fn result_from_duckdb_arrow(code: ffi::duckdb_state, mut out: ffi::duckdb_arrow) -> Result<()> {
267 if code == ffi::DuckDBSuccess {
268 return Ok(());
269 }
270 unsafe {
271 let message = if out.is_null() {
272 Some("out is null".to_string())
273 } else {
274 let c_err = ffi::duckdb_query_arrow_error(out);
275 let message = Some(CStr::from_ptr(c_err).to_string_lossy().to_string());
276 ffi::duckdb_destroy_arrow(&mut out);
277 message
278 };
279 error_from_duckdb_code(code, message)
280 }
281}