1#![allow(dead_code)]
2use crate::ffi::{duckdb_error_type, duckdb_type, Error as FFIError};
5use std::{error, fmt, path::PathBuf, result, str};
6
7#[derive(Debug)]
9pub enum DuckDBConversionError {
10 TypeMismatch {
12 expected: duckdb_type,
14 found: duckdb_type,
16 },
17 ConversionError(String),
19 NullValue,
21 PrecisionLoss(String),
23}
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
37#[non_exhaustive]
38pub enum EngineErrorKind {
39 Invalid,
41 OutOfRange,
43 Conversion,
45 UnknownType,
47 Decimal,
49 MismatchType,
51 DivideByZero,
53 ObjectSize,
55 InvalidType,
57 Serialization,
59 Transaction,
61 NotImplemented,
63 Expression,
65 Catalog,
67 Parser,
69 Planner,
71 Scheduler,
73 Executor,
75 Constraint,
77 Index,
79 Stat,
81 Connection,
83 Syntax,
85 Settings,
87 Binder,
89 Network,
91 Optimizer,
93 NullPointer,
95 Io,
97 Interrupt,
99 Fatal,
101 Internal,
103 InvalidInput,
105 OutOfMemory,
107 Permission,
109 ParameterNotResolved,
111 ParameterNotAllowed,
113 Dependency,
115 Http,
117 MissingExtension,
119 Autoload,
121 Sequence,
123 InvalidConfiguration,
125 Unknown(duckdb_error_type),
128 Unavailable,
132}
133
134impl EngineErrorKind {
135 pub const KNOWN: &'static [EngineErrorKind] = &[
140 EngineErrorKind::Invalid,
141 EngineErrorKind::OutOfRange,
142 EngineErrorKind::Conversion,
143 EngineErrorKind::UnknownType,
144 EngineErrorKind::Decimal,
145 EngineErrorKind::MismatchType,
146 EngineErrorKind::DivideByZero,
147 EngineErrorKind::ObjectSize,
148 EngineErrorKind::InvalidType,
149 EngineErrorKind::Serialization,
150 EngineErrorKind::Transaction,
151 EngineErrorKind::NotImplemented,
152 EngineErrorKind::Expression,
153 EngineErrorKind::Catalog,
154 EngineErrorKind::Parser,
155 EngineErrorKind::Planner,
156 EngineErrorKind::Scheduler,
157 EngineErrorKind::Executor,
158 EngineErrorKind::Constraint,
159 EngineErrorKind::Index,
160 EngineErrorKind::Stat,
161 EngineErrorKind::Connection,
162 EngineErrorKind::Syntax,
163 EngineErrorKind::Settings,
164 EngineErrorKind::Binder,
165 EngineErrorKind::Network,
166 EngineErrorKind::Optimizer,
167 EngineErrorKind::NullPointer,
168 EngineErrorKind::Io,
169 EngineErrorKind::Interrupt,
170 EngineErrorKind::Fatal,
171 EngineErrorKind::Internal,
172 EngineErrorKind::InvalidInput,
173 EngineErrorKind::OutOfMemory,
174 EngineErrorKind::Permission,
175 EngineErrorKind::ParameterNotResolved,
176 EngineErrorKind::ParameterNotAllowed,
177 EngineErrorKind::Dependency,
178 EngineErrorKind::Http,
179 EngineErrorKind::MissingExtension,
180 EngineErrorKind::Autoload,
181 EngineErrorKind::Sequence,
182 EngineErrorKind::InvalidConfiguration,
183 ];
184
185 #[must_use]
187 pub fn from_raw(raw: duckdb_error_type) -> EngineErrorKind {
188 use crate::ffi as f;
189 match raw {
190 f::duckdb_error_type_DUCKDB_ERROR_INVALID => EngineErrorKind::Invalid,
191 f::duckdb_error_type_DUCKDB_ERROR_OUT_OF_RANGE => EngineErrorKind::OutOfRange,
192 f::duckdb_error_type_DUCKDB_ERROR_CONVERSION => EngineErrorKind::Conversion,
193 f::duckdb_error_type_DUCKDB_ERROR_UNKNOWN_TYPE => EngineErrorKind::UnknownType,
194 f::duckdb_error_type_DUCKDB_ERROR_DECIMAL => EngineErrorKind::Decimal,
195 f::duckdb_error_type_DUCKDB_ERROR_MISMATCH_TYPE => EngineErrorKind::MismatchType,
196 f::duckdb_error_type_DUCKDB_ERROR_DIVIDE_BY_ZERO => EngineErrorKind::DivideByZero,
197 f::duckdb_error_type_DUCKDB_ERROR_OBJECT_SIZE => EngineErrorKind::ObjectSize,
198 f::duckdb_error_type_DUCKDB_ERROR_INVALID_TYPE => EngineErrorKind::InvalidType,
199 f::duckdb_error_type_DUCKDB_ERROR_SERIALIZATION => EngineErrorKind::Serialization,
200 f::duckdb_error_type_DUCKDB_ERROR_TRANSACTION => EngineErrorKind::Transaction,
201 f::duckdb_error_type_DUCKDB_ERROR_NOT_IMPLEMENTED => EngineErrorKind::NotImplemented,
202 f::duckdb_error_type_DUCKDB_ERROR_EXPRESSION => EngineErrorKind::Expression,
203 f::duckdb_error_type_DUCKDB_ERROR_CATALOG => EngineErrorKind::Catalog,
204 f::duckdb_error_type_DUCKDB_ERROR_PARSER => EngineErrorKind::Parser,
205 f::duckdb_error_type_DUCKDB_ERROR_PLANNER => EngineErrorKind::Planner,
206 f::duckdb_error_type_DUCKDB_ERROR_SCHEDULER => EngineErrorKind::Scheduler,
207 f::duckdb_error_type_DUCKDB_ERROR_EXECUTOR => EngineErrorKind::Executor,
208 f::duckdb_error_type_DUCKDB_ERROR_CONSTRAINT => EngineErrorKind::Constraint,
209 f::duckdb_error_type_DUCKDB_ERROR_INDEX => EngineErrorKind::Index,
210 f::duckdb_error_type_DUCKDB_ERROR_STAT => EngineErrorKind::Stat,
211 f::duckdb_error_type_DUCKDB_ERROR_CONNECTION => EngineErrorKind::Connection,
212 f::duckdb_error_type_DUCKDB_ERROR_SYNTAX => EngineErrorKind::Syntax,
213 f::duckdb_error_type_DUCKDB_ERROR_SETTINGS => EngineErrorKind::Settings,
214 f::duckdb_error_type_DUCKDB_ERROR_BINDER => EngineErrorKind::Binder,
215 f::duckdb_error_type_DUCKDB_ERROR_NETWORK => EngineErrorKind::Network,
216 f::duckdb_error_type_DUCKDB_ERROR_OPTIMIZER => EngineErrorKind::Optimizer,
217 f::duckdb_error_type_DUCKDB_ERROR_NULL_POINTER => EngineErrorKind::NullPointer,
218 f::duckdb_error_type_DUCKDB_ERROR_IO => EngineErrorKind::Io,
219 f::duckdb_error_type_DUCKDB_ERROR_INTERRUPT => EngineErrorKind::Interrupt,
220 f::duckdb_error_type_DUCKDB_ERROR_FATAL => EngineErrorKind::Fatal,
221 f::duckdb_error_type_DUCKDB_ERROR_INTERNAL => EngineErrorKind::Internal,
222 f::duckdb_error_type_DUCKDB_ERROR_INVALID_INPUT => EngineErrorKind::InvalidInput,
223 f::duckdb_error_type_DUCKDB_ERROR_OUT_OF_MEMORY => EngineErrorKind::OutOfMemory,
224 f::duckdb_error_type_DUCKDB_ERROR_PERMISSION => EngineErrorKind::Permission,
225 f::duckdb_error_type_DUCKDB_ERROR_PARAMETER_NOT_RESOLVED => {
226 EngineErrorKind::ParameterNotResolved
227 },
228 f::duckdb_error_type_DUCKDB_ERROR_PARAMETER_NOT_ALLOWED => {
229 EngineErrorKind::ParameterNotAllowed
230 },
231 f::duckdb_error_type_DUCKDB_ERROR_DEPENDENCY => EngineErrorKind::Dependency,
232 f::duckdb_error_type_DUCKDB_ERROR_HTTP => EngineErrorKind::Http,
233 f::duckdb_error_type_DUCKDB_ERROR_MISSING_EXTENSION => {
234 EngineErrorKind::MissingExtension
235 },
236 f::duckdb_error_type_DUCKDB_ERROR_AUTOLOAD => EngineErrorKind::Autoload,
237 f::duckdb_error_type_DUCKDB_ERROR_SEQUENCE => EngineErrorKind::Sequence,
238 f::duckdb_error_type_DUCKDB_INVALID_CONFIGURATION => {
239 EngineErrorKind::InvalidConfiguration
240 },
241 other => EngineErrorKind::Unknown(other),
242 }
243 }
244
245 #[must_use]
251 pub fn to_raw(self) -> duckdb_error_type {
252 use crate::ffi as f;
253 match self {
254 EngineErrorKind::Invalid => f::duckdb_error_type_DUCKDB_ERROR_INVALID,
255 EngineErrorKind::OutOfRange => f::duckdb_error_type_DUCKDB_ERROR_OUT_OF_RANGE,
256 EngineErrorKind::Conversion => f::duckdb_error_type_DUCKDB_ERROR_CONVERSION,
257 EngineErrorKind::UnknownType => f::duckdb_error_type_DUCKDB_ERROR_UNKNOWN_TYPE,
258 EngineErrorKind::Decimal => f::duckdb_error_type_DUCKDB_ERROR_DECIMAL,
259 EngineErrorKind::MismatchType => f::duckdb_error_type_DUCKDB_ERROR_MISMATCH_TYPE,
260 EngineErrorKind::DivideByZero => f::duckdb_error_type_DUCKDB_ERROR_DIVIDE_BY_ZERO,
261 EngineErrorKind::ObjectSize => f::duckdb_error_type_DUCKDB_ERROR_OBJECT_SIZE,
262 EngineErrorKind::InvalidType => f::duckdb_error_type_DUCKDB_ERROR_INVALID_TYPE,
263 EngineErrorKind::Serialization => f::duckdb_error_type_DUCKDB_ERROR_SERIALIZATION,
264 EngineErrorKind::Transaction => f::duckdb_error_type_DUCKDB_ERROR_TRANSACTION,
265 EngineErrorKind::NotImplemented => f::duckdb_error_type_DUCKDB_ERROR_NOT_IMPLEMENTED,
266 EngineErrorKind::Expression => f::duckdb_error_type_DUCKDB_ERROR_EXPRESSION,
267 EngineErrorKind::Catalog => f::duckdb_error_type_DUCKDB_ERROR_CATALOG,
268 EngineErrorKind::Parser => f::duckdb_error_type_DUCKDB_ERROR_PARSER,
269 EngineErrorKind::Planner => f::duckdb_error_type_DUCKDB_ERROR_PLANNER,
270 EngineErrorKind::Scheduler => f::duckdb_error_type_DUCKDB_ERROR_SCHEDULER,
271 EngineErrorKind::Executor => f::duckdb_error_type_DUCKDB_ERROR_EXECUTOR,
272 EngineErrorKind::Constraint => f::duckdb_error_type_DUCKDB_ERROR_CONSTRAINT,
273 EngineErrorKind::Index => f::duckdb_error_type_DUCKDB_ERROR_INDEX,
274 EngineErrorKind::Stat => f::duckdb_error_type_DUCKDB_ERROR_STAT,
275 EngineErrorKind::Connection => f::duckdb_error_type_DUCKDB_ERROR_CONNECTION,
276 EngineErrorKind::Syntax => f::duckdb_error_type_DUCKDB_ERROR_SYNTAX,
277 EngineErrorKind::Settings => f::duckdb_error_type_DUCKDB_ERROR_SETTINGS,
278 EngineErrorKind::Binder => f::duckdb_error_type_DUCKDB_ERROR_BINDER,
279 EngineErrorKind::Network => f::duckdb_error_type_DUCKDB_ERROR_NETWORK,
280 EngineErrorKind::Optimizer => f::duckdb_error_type_DUCKDB_ERROR_OPTIMIZER,
281 EngineErrorKind::NullPointer => f::duckdb_error_type_DUCKDB_ERROR_NULL_POINTER,
282 EngineErrorKind::Io => f::duckdb_error_type_DUCKDB_ERROR_IO,
283 EngineErrorKind::Interrupt => f::duckdb_error_type_DUCKDB_ERROR_INTERRUPT,
284 EngineErrorKind::Fatal => f::duckdb_error_type_DUCKDB_ERROR_FATAL,
285 EngineErrorKind::Internal => f::duckdb_error_type_DUCKDB_ERROR_INTERNAL,
286 EngineErrorKind::InvalidInput => f::duckdb_error_type_DUCKDB_ERROR_INVALID_INPUT,
287 EngineErrorKind::OutOfMemory => f::duckdb_error_type_DUCKDB_ERROR_OUT_OF_MEMORY,
288 EngineErrorKind::Permission => f::duckdb_error_type_DUCKDB_ERROR_PERMISSION,
289 EngineErrorKind::ParameterNotResolved => {
290 f::duckdb_error_type_DUCKDB_ERROR_PARAMETER_NOT_RESOLVED
291 },
292 EngineErrorKind::ParameterNotAllowed => {
293 f::duckdb_error_type_DUCKDB_ERROR_PARAMETER_NOT_ALLOWED
294 },
295 EngineErrorKind::Dependency => f::duckdb_error_type_DUCKDB_ERROR_DEPENDENCY,
296 EngineErrorKind::Http => f::duckdb_error_type_DUCKDB_ERROR_HTTP,
297 EngineErrorKind::MissingExtension => {
298 f::duckdb_error_type_DUCKDB_ERROR_MISSING_EXTENSION
299 },
300 EngineErrorKind::Autoload => f::duckdb_error_type_DUCKDB_ERROR_AUTOLOAD,
301 EngineErrorKind::Sequence => f::duckdb_error_type_DUCKDB_ERROR_SEQUENCE,
302 EngineErrorKind::InvalidConfiguration => {
303 f::duckdb_error_type_DUCKDB_INVALID_CONFIGURATION
304 },
305 EngineErrorKind::Unknown(raw) => raw,
306 EngineErrorKind::Unavailable => f::duckdb_error_type_DUCKDB_ERROR_INVALID,
307 }
308 }
309}
310
311impl fmt::Display for EngineErrorKind {
312 fn fmt(
313 &self,
314 f: &mut fmt::Formatter<'_>,
315 ) -> fmt::Result {
316 match self {
317 EngineErrorKind::Unknown(raw) => write!(f, "unknown engine error type {raw}"),
318 EngineErrorKind::Unavailable => write!(f, "unclassified engine error"),
319 other => write!(f, "{other:?}"),
320 }
321 }
322}
323
324#[derive(Debug, Clone, PartialEq, Eq)]
330pub struct EngineError {
331 pub kind: EngineErrorKind,
333 pub message: Option<String>,
335}
336
337impl EngineError {
338 #[must_use]
343 pub fn unavailable(message: Option<String>) -> EngineError {
344 EngineError { kind: EngineErrorKind::Unavailable, message }
345 }
346}
347
348impl fmt::Display for EngineError {
349 fn fmt(
350 &self,
351 f: &mut fmt::Formatter<'_>,
352 ) -> fmt::Result {
353 match &self.message {
354 Some(message) => write!(f, "{}: {message}", self.kind),
355 None => write!(f, "{}", self.kind),
356 }
357 }
358}
359
360impl error::Error for EngineError {}
361
362#[derive(Debug)]
364#[allow(clippy::enum_variant_names)]
365#[non_exhaustive]
366pub enum Error {
367 DuckDBFailure(FFIError, Option<String>),
369
370 Engine(EngineError),
372
373 IntegralValueOutOfRange(usize, i128),
382
383 Utf8Error(str::Utf8Error),
385
386 NulError(::std::ffi::NulError),
389
390 InvalidParameterName(String),
393
394 InvalidPath(PathBuf),
396
397 ExecuteReturnedResults,
400
401 QueryReturnedNoRows,
404
405 InvalidColumnIndex(usize),
408
409 InvalidColumnName(String),
412
413 StatementChangedRows(usize),
421
422 ToSqlConversionFailure(Box<dyn error::Error + Send + Sync + 'static>),
425
426 InvalidQuery,
428
429 MultipleStatement,
431
432 InvalidParameterCount(usize, usize),
436
437 AppendError,
439
440 ConversionError(DuckDBConversionError),
442
443 #[allow(non_camel_case_types)]
445 UNKNOWN(Box<dyn ::std::error::Error + Send + Sync + 'static>),
446
447 BackgroundTaskFailed(String),
449
450 Pool(String),
452}
453
454pub type Result<T, E = Error> = result::Result<T, E>;
456
457impl PartialEq for Error {
458 fn eq(
459 &self,
460 other: &Error,
461 ) -> bool {
462 match (self, other) {
463 (Error::DuckDBFailure(e1, s1), Error::DuckDBFailure(e2, s2)) => e1 == e2 && s1 == s2,
464 (Error::Engine(a), Error::Engine(b)) => a == b,
465 (Error::IntegralValueOutOfRange(i1, n1), Error::IntegralValueOutOfRange(i2, n2)) => {
466 i1 == i2 && n1 == n2
467 },
468 (Error::Utf8Error(e1), Error::Utf8Error(e2)) => e1 == e2,
469 (Error::NulError(e1), Error::NulError(e2)) => e1 == e2,
470 (Error::InvalidParameterName(n1), Error::InvalidParameterName(n2)) => n1 == n2,
471 (Error::InvalidPath(p1), Error::InvalidPath(p2)) => p1 == p2,
472 (Error::ExecuteReturnedResults, Error::ExecuteReturnedResults) => true,
473 (Error::QueryReturnedNoRows, Error::QueryReturnedNoRows) => true,
474 (Error::InvalidColumnIndex(i1), Error::InvalidColumnIndex(i2)) => i1 == i2,
475 (Error::InvalidColumnName(n1), Error::InvalidColumnName(n2)) => n1 == n2,
476 (Error::StatementChangedRows(n1), Error::StatementChangedRows(n2)) => n1 == n2,
480 (Error::InvalidParameterCount(i1, n1), Error::InvalidParameterCount(i2, n2)) => {
481 i1 == i2 && n1 == n2
482 },
483 (..) => false,
484 }
485 }
486}
487
488impl From<str::Utf8Error> for Error {
489 #[cold]
490 fn from(err: str::Utf8Error) -> Error {
491 Error::Utf8Error(err)
492 }
493}
494
495impl From<::std::ffi::NulError> for Error {
496 #[cold]
497 fn from(err: ::std::ffi::NulError) -> Error {
498 Error::NulError(err)
499 }
500}
501
502const UNKNOWN_COLUMN: usize = usize::MAX;
503
504impl fmt::Display for Error {
528 fn fmt(
529 &self,
530 f: &mut fmt::Formatter<'_>,
531 ) -> fmt::Result {
532 match self {
533 Error::DuckDBFailure(ref err, None) => err.fmt(f),
534 Error::DuckDBFailure(_, Some(ref s)) => write!(f, "{s}"),
535 Error::Engine(ref err) => err.fmt(f),
536 Error::IntegralValueOutOfRange(col, val) => {
544 if *col != UNKNOWN_COLUMN {
545 write!(f, "Integer {val} out of range at index {col}")
546 } else {
547 write!(f, "Integer {val} out of range")
548 }
549 },
550 Error::Utf8Error(ref err) => err.fmt(f),
551 Error::NulError(ref err) => err.fmt(f),
552 Error::InvalidParameterName(ref name) => write!(f, "Invalid parameter name: {name}"),
553 Error::InvalidPath(ref p) => write!(f, "Invalid path: {}", p.to_string_lossy()),
554 Error::ExecuteReturnedResults => {
555 write!(f, "Execute returned results - did you mean to call query?")
556 },
557 Error::QueryReturnedNoRows => write!(f, "Query returned no rows"),
558 Error::InvalidColumnIndex(i) => write!(f, "Invalid column index: {i}"),
559 Error::InvalidColumnName(ref name) => write!(f, "Invalid column name: {name}"),
560 Error::InvalidParameterCount(i1, n1) => {
567 write!(f, "Wrong number of parameters passed to query. Got {i1}, needed {n1}")
568 },
569 Error::StatementChangedRows(i) => write!(f, "Query changed {i} rows"),
570 Error::ToSqlConversionFailure(ref err) => err.fmt(f),
571 Error::InvalidQuery => write!(f, "Query is not read-only"),
572 Error::MultipleStatement => write!(f, "Multiple statements provided"),
573 Error::AppendError => write!(f, "Append error"),
574 Error::ConversionError(ref err) => match err {
575 DuckDBConversionError::TypeMismatch { expected, found } => {
576 write!(f, "Type mismatch: expected {expected}, found {found}")
577 },
578 DuckDBConversionError::ConversionError(ref msg) => {
579 write!(f, "Conversion error: {msg}")
580 },
581 DuckDBConversionError::NullValue => write!(f, "Null value encountered"),
582 DuckDBConversionError::PrecisionLoss(ref msg) => write!(f, "Precision loss: {msg}"),
583 },
584 Error::UNKNOWN(e) => write!(f, "Unknown error: {e}"),
585 Error::BackgroundTaskFailed(ref msg) => write!(f, "Background task failed: {msg}"),
586 Error::Pool(ref msg) => write!(f, "Connection pool error: {msg}"),
587 }
588 }
589}
590
591impl error::Error for Error {
592 fn source(&self) -> Option<&(dyn error::Error + 'static)> {
593 match self {
594 Error::DuckDBFailure(ref err, _) => Some(err),
595 Error::Engine(ref err) => Some(err),
596 Error::Utf8Error(ref err) => Some(err),
597 Error::NulError(ref err) => Some(err),
598
599 Error::IntegralValueOutOfRange(..)
600 | Error::InvalidParameterName(_)
601 | Error::ExecuteReturnedResults
602 | Error::QueryReturnedNoRows
603 | Error::InvalidColumnIndex(_)
604 | Error::InvalidColumnName(_)
605 | Error::InvalidPath(_)
607 | Error::InvalidParameterCount(..)
608 | Error::StatementChangedRows(_)
609 | Error::InvalidQuery
610 | Error::AppendError
611 | Error::MultipleStatement
613 | Error::ConversionError(_) => None,
614 Error::ToSqlConversionFailure(ref err) => Some(&**err),
616 Error::UNKNOWN(e) => Some(e.as_ref()),
617 Error::BackgroundTaskFailed(_) | Error::Pool(_) => None,
618 }
619 }
620}
621
622#[cfg(test)]
623mod tests {
624 use super::*;
625 use crate::ffi::{DuckDBError, DuckDBSuccess};
626 use std::{error::Error as _, ffi::CString, io};
627
628 fn invalid_utf8() -> str::Utf8Error {
629 let bytes = vec![0xff];
630 str::from_utf8(&bytes).unwrap_err()
631 }
632
633 #[test]
634 fn equality_covers_comparable_variants_and_rejects_others() {
635 assert_eq!(
636 Error::DuckDBFailure(FFIError::new(DuckDBError), Some("context".into())),
637 Error::DuckDBFailure(FFIError::new(DuckDBError), Some("context".into()))
638 );
639 assert_ne!(
640 Error::DuckDBFailure(FFIError::new(DuckDBSuccess), None),
641 Error::DuckDBFailure(FFIError::new(DuckDBError), None)
642 );
643 assert_eq!(Error::IntegralValueOutOfRange(2, 300), Error::IntegralValueOutOfRange(2, 300));
644 assert_ne!(Error::IntegralValueOutOfRange(2, 300), Error::IntegralValueOutOfRange(3, 300));
645 assert_eq!(Error::Utf8Error(invalid_utf8()), Error::Utf8Error(invalid_utf8()));
646 assert_eq!(
647 Error::NulError(CString::new("a\0b").unwrap_err()),
648 Error::NulError(CString::new("a\0b").unwrap_err())
649 );
650 assert_eq!(
651 Error::InvalidParameterName("p".into()),
652 Error::InvalidParameterName("p".into())
653 );
654 assert_eq!(
655 Error::InvalidPath(PathBuf::from("file.db")),
656 Error::InvalidPath(PathBuf::from("file.db"))
657 );
658 assert_eq!(Error::ExecuteReturnedResults, Error::ExecuteReturnedResults);
659 assert_eq!(Error::QueryReturnedNoRows, Error::QueryReturnedNoRows);
660 assert_eq!(Error::InvalidColumnIndex(4), Error::InvalidColumnIndex(4));
661 assert_eq!(
662 Error::InvalidColumnName("value".into()),
663 Error::InvalidColumnName("value".into())
664 );
665 assert_eq!(Error::StatementChangedRows(3), Error::StatementChangedRows(3));
666 assert_eq!(Error::InvalidParameterCount(1, 2), Error::InvalidParameterCount(1, 2));
667 assert_ne!(
668 Error::ToSqlConversionFailure(Box::new(io::Error::other("same"))),
669 Error::ToSqlConversionFailure(Box::new(io::Error::other("same")))
670 );
671 assert_ne!(Error::InvalidQuery, Error::InvalidQuery);
672 assert_ne!(Error::MultipleStatement, Error::MultipleStatement);
673 assert_ne!(Error::AppendError, Error::AppendError);
674 assert_ne!(
675 Error::ConversionError(DuckDBConversionError::TypeMismatch {
676 expected: crate::ffi::DUCKDB_TYPE_DUCKDB_TYPE_INTEGER,
677 found: crate::ffi::DUCKDB_TYPE_DUCKDB_TYPE_VARCHAR
678 }),
679 Error::ConversionError(DuckDBConversionError::TypeMismatch {
680 expected: crate::ffi::DUCKDB_TYPE_DUCKDB_TYPE_INTEGER,
681 found: crate::ffi::DUCKDB_TYPE_DUCKDB_TYPE_VARCHAR
682 })
683 );
684 assert_ne!(
685 Error::ConversionError(DuckDBConversionError::NullValue),
686 Error::ConversionError(DuckDBConversionError::NullValue)
687 );
688 assert_ne!(
689 Error::BackgroundTaskFailed("same".into()),
690 Error::BackgroundTaskFailed("same".into())
691 );
692 assert_ne!(Error::Pool("same".into()), Error::Pool("same".into()));
693 assert_ne!(Error::InvalidQuery, Error::MultipleStatement);
694 }
695
696 #[test]
697 fn utf8_and_nul_errors_convert_and_compare() {
698 let utf8 = invalid_utf8();
699 let converted: Error = utf8.into();
700 assert_eq!(converted, Error::Utf8Error(utf8));
701
702 let nul = CString::new("a\0b").unwrap_err();
703 let expected = CString::new("a\0b").unwrap_err();
704 let converted: Error = nul.into();
705 assert_eq!(converted, Error::NulError(expected));
706 }
707
708 #[test]
709 fn display_formats_error_context() {
710 assert_eq!(
711 Error::IntegralValueOutOfRange(2, 300).to_string(),
712 "Integer 300 out of range at index 2"
713 );
714 assert_eq!(
715 Error::IntegralValueOutOfRange(usize::MAX, 300).to_string(),
716 "Integer 300 out of range"
717 );
718 assert_eq!(
719 Error::ConversionError(DuckDBConversionError::ConversionError("bad value".into()))
720 .to_string(),
721 "Conversion error: bad value"
722 );
723 assert_eq!(
724 Error::ConversionError(DuckDBConversionError::NullValue).to_string(),
725 "Null value encountered"
726 );
727 assert_eq!(
728 Error::ConversionError(DuckDBConversionError::PrecisionLoss("rounded".into()))
729 .to_string(),
730 "Precision loss: rounded"
731 );
732 assert_eq!(Error::AppendError.to_string(), "Append error");
733 assert_eq!(Error::InvalidQuery.to_string(), "Query is not read-only");
734 assert_eq!(Error::MultipleStatement.to_string(), "Multiple statements provided");
735 assert_eq!(
736 Error::BackgroundTaskFailed("worker stopped".into()).to_string(),
737 "Background task failed: worker stopped"
738 );
739 assert_eq!(Error::Pool("timed out".into()).to_string(), "Connection pool error: timed out");
740 assert_eq!(
741 Error::DuckDBFailure(FFIError::new(DuckDBError), Some("query failed".into()))
742 .to_string(),
743 "query failed"
744 );
745 assert_eq!(
746 Error::DuckDBFailure(FFIError::new(DuckDBError), None).to_string(),
747 "DuckDB call failed with result code 1"
748 );
749 }
750
751 #[test]
752 fn engine_error_kinds_round_trip_and_preserve_unknown_values() {
753 for kind in EngineErrorKind::KNOWN {
755 assert_eq!(
756 EngineErrorKind::from_raw(kind.to_raw()),
757 *kind,
758 "kind {kind:?} did not round-trip"
759 );
760 }
761
762 let future = EngineErrorKind::from_raw(9_999);
764 assert_eq!(future, EngineErrorKind::Unknown(9_999));
765 assert_eq!(future.to_raw(), 9_999);
766 assert_eq!(future.to_string(), "unknown engine error type 9999");
767
768 assert_ne!(EngineErrorKind::Unavailable, EngineErrorKind::Unknown(0));
770 assert_eq!(EngineErrorKind::Unavailable.to_string(), "unclassified engine error");
771 }
772
773 #[test]
774 fn engine_errors_display_and_compare_by_value() {
775 let with_message =
776 EngineError { kind: EngineErrorKind::Catalog, message: Some("no such table".into()) };
777 assert_eq!(with_message.to_string(), "Catalog: no such table");
778 assert_eq!(with_message, with_message.clone());
779
780 let without = EngineError { kind: EngineErrorKind::Binder, message: None };
781 assert_eq!(without.to_string(), "Binder");
782
783 assert_eq!(EngineError::unavailable(None).kind, EngineErrorKind::Unavailable);
784
785 let wrapped = Error::Engine(with_message.clone());
787 assert_eq!(wrapped.to_string(), "Catalog: no such table");
788 assert!(wrapped.source().is_some());
789 assert_eq!(wrapped, Error::Engine(with_message));
790 assert_ne!(
791 Error::Engine(EngineError::unavailable(None)),
792 Error::Engine(EngineError { kind: EngineErrorKind::Io, message: None })
793 );
794 }
795
796 #[test]
797 fn sources_are_exposed_only_for_wrapped_errors() {
798 let utf8 = Error::Utf8Error(invalid_utf8());
799 let nul = Error::NulError(CString::new("a\0b").unwrap_err());
800 let ffi = Error::DuckDBFailure(FFIError::new(DuckDBError), None);
801 let sql = Error::ToSqlConversionFailure(Box::new(io::Error::other("sql")));
802 let unknown = Error::UNKNOWN(Box::new(io::Error::other("unknown")));
803
804 assert!(ffi.source().is_some());
805 assert!(utf8.source().is_some());
806 assert!(nul.source().is_some());
807 assert_eq!(sql.source().unwrap().to_string(), "sql");
808 assert_eq!(unknown.source().unwrap().to_string(), "unknown");
809 assert!(Error::AppendError.source().is_none());
810 assert!(Error::ConversionError(DuckDBConversionError::NullValue).source().is_none());
811 assert!(Error::BackgroundTaskFailed("stopped".into()).source().is_none());
812 assert!(Error::Pool("timeout".into()).source().is_none());
813 }
814}