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 UnsignedIntegralValueOutOfRange(usize, u128),
32
33 Utf8Error(str::Utf8Error),
35
36 NulError(::std::ffi::NulError),
39
40 InvalidParameterName(String),
43
44 InvalidPath(PathBuf),
46
47 ExecuteReturnedResults,
50
51 QueryReturnedNoRows,
54
55 QueryReturnedMoreThanOneRow,
59
60 InvalidColumnIndex(usize),
63
64 InvalidColumnName(String),
67
68 InvalidColumnType(usize, String, Type),
72
73 ArrowTypeToDuckdbType(String, DataType),
75
76 StatementChangedRows(usize),
79
80 ToSqlConversionFailure(Box<dyn error::Error + Send + Sync + 'static>),
83
84 InvalidQuery,
86
87 MultipleStatement,
89
90 InvalidParameterCount(usize, usize),
94
95 InvalidParameterIndex(usize),
98
99 AppendError,
101}
102
103impl PartialEq for Error {
104 fn eq(&self, other: &Self) -> bool {
105 match (self, other) {
106 (Self::DuckDBFailure(e1, s1), Self::DuckDBFailure(e2, s2)) => e1 == e2 && s1 == s2,
107 (Self::IntegralValueOutOfRange(i1, n1), Self::IntegralValueOutOfRange(i2, n2)) => i1 == i2 && n1 == n2,
108 (Self::UnsignedIntegralValueOutOfRange(i1, n1), Self::UnsignedIntegralValueOutOfRange(i2, n2)) => {
109 i1 == i2 && n1 == n2
110 }
111 (Self::Utf8Error(e1), Self::Utf8Error(e2)) => e1 == e2,
112 (Self::NulError(e1), Self::NulError(e2)) => e1 == e2,
113 (Self::InvalidParameterName(n1), Self::InvalidParameterName(n2)) => n1 == n2,
114 (Self::InvalidPath(p1), Self::InvalidPath(p2)) => p1 == p2,
115 (Self::ExecuteReturnedResults, Self::ExecuteReturnedResults) => true,
116 (Self::QueryReturnedNoRows, Self::QueryReturnedNoRows) => true,
117 (Self::QueryReturnedMoreThanOneRow, Self::QueryReturnedMoreThanOneRow) => true,
118 (Self::InvalidColumnIndex(i1), Self::InvalidColumnIndex(i2)) => i1 == i2,
119 (Self::InvalidColumnName(n1), Self::InvalidColumnName(n2)) => n1 == n2,
120 (Self::InvalidColumnType(i1, n1, t1), Self::InvalidColumnType(i2, n2, t2)) => {
121 i1 == i2 && t1 == t2 && n1 == n2
122 }
123 (Self::StatementChangedRows(n1), Self::StatementChangedRows(n2)) => n1 == n2,
124 (Self::InvalidParameterCount(i1, n1), Self::InvalidParameterCount(i2, n2)) => i1 == i2 && n1 == n2,
125 (Self::InvalidParameterIndex(i1), Self::InvalidParameterIndex(i2)) => i1 == i2,
126 (..) => false,
127 }
128 }
129}
130
131impl From<str::Utf8Error> for Error {
132 #[cold]
133 fn from(err: str::Utf8Error) -> Self {
134 Self::Utf8Error(err)
135 }
136}
137
138impl From<::std::ffi::NulError> for Error {
139 #[cold]
140 fn from(err: ::std::ffi::NulError) -> Self {
141 Self::NulError(err)
142 }
143}
144
145const UNKNOWN_COLUMN: usize = usize::MAX;
146
147impl From<FromSqlError> for Error {
150 #[cold]
151 fn from(err: FromSqlError) -> Self {
152 match err {
155 FromSqlError::OutOfRange(val) => Self::IntegralValueOutOfRange(UNKNOWN_COLUMN, val),
156 FromSqlError::OutOfRangeUnsigned(val) => Self::UnsignedIntegralValueOutOfRange(UNKNOWN_COLUMN, val),
157 #[cfg(feature = "uuid")]
158 FromSqlError::InvalidUuidSize(_) => {
159 Self::FromSqlConversionFailure(UNKNOWN_COLUMN, Type::Blob, Box::new(err))
160 }
161 FromSqlError::Other(source) => Self::FromSqlConversionFailure(UNKNOWN_COLUMN, Type::Null, source),
162 _ => Self::FromSqlConversionFailure(UNKNOWN_COLUMN, Type::Null, Box::new(err)),
163 }
164 }
165}
166
167impl fmt::Display for Error {
168 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
169 match *self {
170 Self::DuckDBFailure(ref err, None) => err.fmt(f),
171 Self::DuckDBFailure(_, Some(ref s)) => write!(f, "{s}"),
172 Self::FromSqlConversionFailure(i, ref t, ref err) => {
173 if i != UNKNOWN_COLUMN {
174 write!(f, "Conversion error from type {t} at index: {i}, {err}")
175 } else {
176 err.fmt(f)
177 }
178 }
179 Self::IntegralValueOutOfRange(col, val) => {
180 if col != UNKNOWN_COLUMN {
181 write!(f, "Integer {val} out of range at index {col}")
182 } else {
183 write!(f, "Integer {val} out of range")
184 }
185 }
186 Self::UnsignedIntegralValueOutOfRange(col, val) => {
187 if col != UNKNOWN_COLUMN {
188 write!(f, "Unsigned integer {val} out of range at index {col}")
189 } else {
190 write!(f, "Unsigned integer {val} out of range")
191 }
192 }
193 Self::Utf8Error(ref err) => err.fmt(f),
194 Self::NulError(ref err) => err.fmt(f),
195 Self::InvalidParameterName(ref name) => write!(f, "Invalid parameter name: {name}"),
196 Self::InvalidPath(ref p) => write!(f, "Invalid path: {}", p.to_string_lossy()),
197 Self::ExecuteReturnedResults => {
198 write!(f, "Execute returned results - did you mean to call query?")
199 }
200 Self::QueryReturnedNoRows => write!(f, "Query returned no rows"),
201 Self::QueryReturnedMoreThanOneRow => write!(f, "Query returned more than one row"),
202 Self::InvalidColumnIndex(i) => write!(f, "Invalid column index: {i}"),
203 Self::InvalidColumnName(ref name) => write!(f, "Invalid column name: {name}"),
204 Self::InvalidColumnType(i, ref name, ref t) => {
205 write!(f, "Invalid column type {t} at index: {i}, name: {name}")
206 }
207 Self::ArrowTypeToDuckdbType(ref name, ref t) => {
208 write!(f, "Invalid column type {t} , name: {name}")
209 }
210 Self::InvalidParameterCount(i1, n1) => {
211 write!(f, "Wrong number of parameters passed to query. Got {i1}, needed {n1}")
212 }
213 Self::InvalidParameterIndex(i) => write!(f, "Invalid parameter index: {i}"),
214 Self::StatementChangedRows(i) => write!(f, "Query changed {i} rows"),
215 Self::ToSqlConversionFailure(ref err) => err.fmt(f),
216 Self::InvalidQuery => write!(f, "Query is not read-only"),
217 Self::MultipleStatement => write!(f, "Multiple statements provided"),
218 Self::AppendError => write!(f, "Append error"),
219 }
220 }
221}
222
223impl error::Error for Error {
224 fn source(&self) -> Option<&(dyn error::Error + 'static)> {
225 match *self {
226 Self::DuckDBFailure(ref err, _) => Some(err),
227 Self::Utf8Error(ref err) => Some(err),
228 Self::NulError(ref err) => Some(err),
229
230 Self::IntegralValueOutOfRange(..)
231 | Self::UnsignedIntegralValueOutOfRange(..)
232 | Self::InvalidParameterName(_)
233 | Self::ExecuteReturnedResults
234 | Self::QueryReturnedNoRows
235 | Self::QueryReturnedMoreThanOneRow
236 | Self::InvalidColumnIndex(_)
237 | Self::InvalidColumnName(_)
238 | Self::InvalidColumnType(..)
239 | Self::InvalidPath(_)
240 | Self::InvalidParameterCount(..)
241 | Self::InvalidParameterIndex(_)
242 | Self::StatementChangedRows(_)
243 | Self::InvalidQuery
244 | Self::AppendError
245 | Self::ArrowTypeToDuckdbType(..)
246 | Self::MultipleStatement => None,
247 Self::FromSqlConversionFailure(_, _, ref err) | Self::ToSqlConversionFailure(ref err) => Some(&**err),
248 }
249 }
250}
251
252#[inline]
255fn result_from_duckdb_state(code: ffi::duckdb_state, message: Option<String>) -> Result<()> {
256 Err(duckdb_failure_from_state(code, message))
257}
258
259#[inline]
260pub(crate) fn duckdb_failure_from_state(code: ffi::duckdb_state, message: Option<String>) -> Error {
261 Error::DuckDBFailure(ffi::Error::new(code), message)
262}
263
264#[inline]
265fn duckdb_failure_from_error_type(error_type: ffi::duckdb_error_type, message: Option<String>) -> Error {
266 Error::DuckDBFailure(ffi::Error::new(error_type as ffi::duckdb_state), message)
267}
268
269#[inline]
270pub(crate) fn duckdb_failure_from_message(message: impl Into<String>) -> Error {
271 duckdb_failure_from_state(ffi::DuckDBError, Some(message.into()))
272}
273
274#[inline]
275pub(crate) fn arrow_conversion_failure(context: &str, err: impl fmt::Display) -> Error {
276 duckdb_failure_from_message(format!("{context}: {err}"))
277}
278
279#[inline]
280fn appender_error_message(appender: ffi::duckdb_appender) -> Option<String> {
281 if appender.is_null() {
282 return Some("appender is null".to_string());
283 }
284 unsafe {
285 let c_err = ffi::duckdb_appender_error(appender);
286 if c_err.is_null() {
287 None
288 } else {
289 Some(CStr::from_ptr(c_err).to_string_lossy().into_owned())
290 }
291 }
292}
293
294#[inline]
295pub(crate) fn error_from_appender_code(code: ffi::duckdb_state, appender: ffi::duckdb_appender) -> Error {
296 Error::DuckDBFailure(ffi::Error::new(code), appender_error_message(appender))
297}
298
299#[cold]
300#[inline]
301pub fn result_from_duckdb_appender(code: ffi::duckdb_state, appender: *mut ffi::duckdb_appender) -> Result<()> {
302 if code == ffi::DuckDBSuccess {
303 return Ok(());
304 }
305 unsafe {
306 let message = appender_error_message(*appender);
307 if !(*appender).is_null() {
308 ffi::duckdb_appender_destroy(appender);
309 }
310 result_from_duckdb_state(code, message)
311 }
312}
313
314#[cold]
315#[inline]
316pub fn result_from_duckdb_prepare(code: ffi::duckdb_state, mut prepare: ffi::duckdb_prepared_statement) -> Result<()> {
317 if code == ffi::DuckDBSuccess {
318 return Ok(());
319 }
320 unsafe {
321 let message = if prepare.is_null() {
322 Some("prepare is null".to_string())
323 } else {
324 let c_err = ffi::duckdb_prepare_error(prepare);
325 let message = Some(CStr::from_ptr(c_err).to_string_lossy().to_string());
326 ffi::duckdb_destroy_prepare(&mut prepare);
327 message
328 };
329 result_from_duckdb_state(code, message)
330 }
331}
332
333#[cold]
334#[inline]
335pub fn result_from_duckdb_result(code: ffi::duckdb_state, result: *mut ffi::duckdb_result) -> Result<()> {
336 if code == ffi::DuckDBSuccess {
337 return Ok(());
338 }
339 unsafe {
340 if result.is_null() {
341 result_from_duckdb_state(code, Some("result is null".to_string()))
342 } else {
343 let message = result_error_message(result);
344 ffi::duckdb_destroy_result(result);
345 result_from_duckdb_state(code, message)
346 }
347 }
348}
349
350pub(crate) unsafe fn result_error_message(result: *mut ffi::duckdb_result) -> Option<String> {
356 unsafe {
357 debug_assert!(!result.is_null());
358 let c_err = ffi::duckdb_result_error(result);
359 if c_err.is_null() {
360 None
361 } else {
362 Some(CStr::from_ptr(c_err).to_string_lossy().to_string())
363 }
364 }
365}
366
367#[inline]
375pub(crate) unsafe fn result_from_duckdb_error_data(mut error_data: ffi::duckdb_error_data) -> Result<()> {
376 unsafe {
377 if error_data.is_null() {
378 return Ok(());
379 }
380 if !ffi::duckdb_error_data_has_error(error_data) {
381 ffi::duckdb_destroy_error_data(&mut error_data);
382 return Ok(());
383 }
384
385 let error_type = ffi::duckdb_error_data_error_type(error_data);
386 let message = {
387 let c_err = ffi::duckdb_error_data_message(error_data);
388 if c_err.is_null() {
389 None
390 } else {
391 Some(CStr::from_ptr(c_err).to_string_lossy().to_string())
392 }
393 };
394 ffi::duckdb_destroy_error_data(&mut error_data);
395 Err(duckdb_failure_from_error_type(error_type, message))
396 }
397}
398
399#[cold]
400#[inline]
401pub fn result_from_duckdb_extract(
402 num_statements: ffi::idx_t,
403 mut extracted: ffi::duckdb_extracted_statements,
404) -> Result<()> {
405 if num_statements > 0 {
406 return Ok(());
407 }
408 unsafe {
409 let message = if extracted.is_null() {
410 Some("extracted statements are null".to_string())
411 } else {
412 let c_err = ffi::duckdb_extract_statements_error(extracted);
413 let message = if c_err.is_null() {
414 None
415 } else {
416 Some(CStr::from_ptr(c_err).to_string_lossy().to_string())
417 };
418 ffi::duckdb_destroy_extracted(&mut extracted);
419 message
420 };
421 result_from_duckdb_state(ffi::DuckDBError, message)
422 }
423}
424
425#[cfg(test)]
426mod tests {
427 use std::{ffi::CString, ptr};
428
429 use super::{Error, result_from_duckdb_error_data};
430 use crate::ffi;
431
432 #[test]
433 fn result_from_duckdb_error_data_accepts_null() {
434 unsafe {
435 result_from_duckdb_error_data(ptr::null_mut()).unwrap();
436 }
437 }
438
439 #[test]
440 fn result_from_duckdb_error_data_accepts_no_error_handle() {
441 unsafe {
442 let mut db = ptr::null_mut();
443 assert_eq!(ffi::DuckDBSuccess, ffi::duckdb_open(ptr::null(), &mut db));
444
445 let mut con = ptr::null_mut();
446 assert_eq!(ffi::DuckDBSuccess, ffi::duckdb_connect(db, &mut con));
447
448 let sql = CString::new("CREATE TABLE t(i INTEGER)").unwrap();
449 let mut result: ffi::duckdb_result = std::mem::zeroed();
450 assert_eq!(ffi::DuckDBSuccess, ffi::duckdb_query(con, sql.as_ptr(), &mut result));
451 ffi::duckdb_destroy_result(&mut result);
452
453 let table = CString::new("t").unwrap();
454 let mut appender = ptr::null_mut();
455 assert_eq!(
456 ffi::DuckDBSuccess,
457 ffi::duckdb_appender_create(con, ptr::null(), table.as_ptr(), &mut appender)
458 );
459
460 let error_data = ffi::duckdb_appender_error_data(appender);
461 assert!(!error_data.is_null());
462 assert!(!ffi::duckdb_error_data_has_error(error_data));
463
464 result_from_duckdb_error_data(error_data).unwrap();
465
466 assert_eq!(ffi::DuckDBSuccess, ffi::duckdb_appender_destroy(&mut appender));
467 ffi::duckdb_disconnect(&mut con);
468 ffi::duckdb_close(&mut db);
469 }
470 }
471
472 #[test]
473 fn result_from_duckdb_error_data_returns_error_message() {
474 let message = CString::new("synthetic error").unwrap();
475 let error_data =
476 unsafe { ffi::duckdb_create_error_data(ffi::duckdb_error_type_DUCKDB_ERROR_INTERNAL, message.as_ptr()) };
477
478 let err = unsafe { result_from_duckdb_error_data(error_data) }.unwrap_err();
479
480 match err {
481 Error::DuckDBFailure(err, Some(message)) => {
482 assert_eq!(
483 err.extended_code,
484 ffi::duckdb_error_type_DUCKDB_ERROR_INTERNAL as ffi::duckdb_state
485 );
486 assert_eq!(message, "synthetic error");
487 }
488 err => panic!("unexpected error: {err:?}"),
489 }
490 }
491}