1use std::ffi::{CStr, CString};
31use std::fmt;
32
33use libduckdb_sys::{
34 duckdb_create_error_data, duckdb_destroy_error_data, duckdb_error_data,
35 duckdb_error_data_error_type, duckdb_error_data_has_error, duckdb_error_data_message,
36 duckdb_error_type, duckdb_error_type_DUCKDB_ERROR_BINDER,
37 duckdb_error_type_DUCKDB_ERROR_CATALOG, duckdb_error_type_DUCKDB_ERROR_CONNECTION,
38 duckdb_error_type_DUCKDB_ERROR_CONSTRAINT, duckdb_error_type_DUCKDB_ERROR_CONVERSION,
39 duckdb_error_type_DUCKDB_ERROR_DECIMAL, duckdb_error_type_DUCKDB_ERROR_DEPENDENCY,
40 duckdb_error_type_DUCKDB_ERROR_DIVIDE_BY_ZERO, duckdb_error_type_DUCKDB_ERROR_EXECUTOR,
41 duckdb_error_type_DUCKDB_ERROR_EXPRESSION, duckdb_error_type_DUCKDB_ERROR_FATAL,
42 duckdb_error_type_DUCKDB_ERROR_HTTP, duckdb_error_type_DUCKDB_ERROR_INDEX,
43 duckdb_error_type_DUCKDB_ERROR_INTERNAL, duckdb_error_type_DUCKDB_ERROR_INTERRUPT,
44 duckdb_error_type_DUCKDB_ERROR_INVALID, duckdb_error_type_DUCKDB_ERROR_INVALID_INPUT,
45 duckdb_error_type_DUCKDB_ERROR_INVALID_TYPE, duckdb_error_type_DUCKDB_ERROR_IO,
46 duckdb_error_type_DUCKDB_ERROR_MISMATCH_TYPE, duckdb_error_type_DUCKDB_ERROR_MISSING_EXTENSION,
47 duckdb_error_type_DUCKDB_ERROR_NETWORK, duckdb_error_type_DUCKDB_ERROR_NOT_IMPLEMENTED,
48 duckdb_error_type_DUCKDB_ERROR_NULL_POINTER, duckdb_error_type_DUCKDB_ERROR_OBJECT_SIZE,
49 duckdb_error_type_DUCKDB_ERROR_OPTIMIZER, duckdb_error_type_DUCKDB_ERROR_OUT_OF_MEMORY,
50 duckdb_error_type_DUCKDB_ERROR_OUT_OF_RANGE,
51 duckdb_error_type_DUCKDB_ERROR_PARAMETER_NOT_ALLOWED,
52 duckdb_error_type_DUCKDB_ERROR_PARAMETER_NOT_RESOLVED, duckdb_error_type_DUCKDB_ERROR_PARSER,
53 duckdb_error_type_DUCKDB_ERROR_PERMISSION, duckdb_error_type_DUCKDB_ERROR_PLANNER,
54 duckdb_error_type_DUCKDB_ERROR_SCHEDULER, duckdb_error_type_DUCKDB_ERROR_SERIALIZATION,
55 duckdb_error_type_DUCKDB_ERROR_SETTINGS, duckdb_error_type_DUCKDB_ERROR_STAT,
56 duckdb_error_type_DUCKDB_ERROR_SYNTAX, duckdb_error_type_DUCKDB_ERROR_TRANSACTION,
57 duckdb_error_type_DUCKDB_ERROR_UNKNOWN_TYPE, duckdb_valid_utf8_check, idx_t,
58};
59
60use crate::error::ExtensionError;
61
62#[derive(Debug, Clone, Copy, PartialEq, Eq)]
66#[non_exhaustive]
67pub enum DuckDbErrorType {
68 Invalid,
70 OutOfRange,
72 Conversion,
74 UnknownType,
76 Decimal,
78 MismatchType,
80 DivideByZero,
82 ObjectSize,
84 InvalidType,
86 Serialization,
88 Transaction,
90 NotImplemented,
92 Expression,
94 Catalog,
96 Parser,
98 Planner,
100 Scheduler,
102 Executor,
104 Constraint,
106 Index,
108 Stat,
110 Connection,
112 Syntax,
114 Settings,
116 Binder,
118 Network,
120 Optimizer,
122 NullPointer,
124 Io,
126 Interrupt,
128 Fatal,
130 Internal,
132 InvalidInput,
134 OutOfMemory,
136 Permission,
138 ParameterNotResolved,
140 ParameterNotAllowed,
142 Dependency,
144 Http,
146 MissingExtension,
148}
149
150impl DuckDbErrorType {
151 #[must_use]
153 pub(crate) const fn to_raw(self) -> duckdb_error_type {
154 match self {
155 Self::Invalid => duckdb_error_type_DUCKDB_ERROR_INVALID,
156 Self::OutOfRange => duckdb_error_type_DUCKDB_ERROR_OUT_OF_RANGE,
157 Self::Conversion => duckdb_error_type_DUCKDB_ERROR_CONVERSION,
158 Self::UnknownType => duckdb_error_type_DUCKDB_ERROR_UNKNOWN_TYPE,
159 Self::Decimal => duckdb_error_type_DUCKDB_ERROR_DECIMAL,
160 Self::MismatchType => duckdb_error_type_DUCKDB_ERROR_MISMATCH_TYPE,
161 Self::DivideByZero => duckdb_error_type_DUCKDB_ERROR_DIVIDE_BY_ZERO,
162 Self::ObjectSize => duckdb_error_type_DUCKDB_ERROR_OBJECT_SIZE,
163 Self::InvalidType => duckdb_error_type_DUCKDB_ERROR_INVALID_TYPE,
164 Self::Serialization => duckdb_error_type_DUCKDB_ERROR_SERIALIZATION,
165 Self::Transaction => duckdb_error_type_DUCKDB_ERROR_TRANSACTION,
166 Self::NotImplemented => duckdb_error_type_DUCKDB_ERROR_NOT_IMPLEMENTED,
167 Self::Expression => duckdb_error_type_DUCKDB_ERROR_EXPRESSION,
168 Self::Catalog => duckdb_error_type_DUCKDB_ERROR_CATALOG,
169 Self::Parser => duckdb_error_type_DUCKDB_ERROR_PARSER,
170 Self::Planner => duckdb_error_type_DUCKDB_ERROR_PLANNER,
171 Self::Scheduler => duckdb_error_type_DUCKDB_ERROR_SCHEDULER,
172 Self::Executor => duckdb_error_type_DUCKDB_ERROR_EXECUTOR,
173 Self::Constraint => duckdb_error_type_DUCKDB_ERROR_CONSTRAINT,
174 Self::Index => duckdb_error_type_DUCKDB_ERROR_INDEX,
175 Self::Stat => duckdb_error_type_DUCKDB_ERROR_STAT,
176 Self::Connection => duckdb_error_type_DUCKDB_ERROR_CONNECTION,
177 Self::Syntax => duckdb_error_type_DUCKDB_ERROR_SYNTAX,
178 Self::Settings => duckdb_error_type_DUCKDB_ERROR_SETTINGS,
179 Self::Binder => duckdb_error_type_DUCKDB_ERROR_BINDER,
180 Self::Network => duckdb_error_type_DUCKDB_ERROR_NETWORK,
181 Self::Optimizer => duckdb_error_type_DUCKDB_ERROR_OPTIMIZER,
182 Self::NullPointer => duckdb_error_type_DUCKDB_ERROR_NULL_POINTER,
183 Self::Io => duckdb_error_type_DUCKDB_ERROR_IO,
184 Self::Interrupt => duckdb_error_type_DUCKDB_ERROR_INTERRUPT,
185 Self::Fatal => duckdb_error_type_DUCKDB_ERROR_FATAL,
186 Self::Internal => duckdb_error_type_DUCKDB_ERROR_INTERNAL,
187 Self::InvalidInput => duckdb_error_type_DUCKDB_ERROR_INVALID_INPUT,
188 Self::OutOfMemory => duckdb_error_type_DUCKDB_ERROR_OUT_OF_MEMORY,
189 Self::Permission => duckdb_error_type_DUCKDB_ERROR_PERMISSION,
190 Self::ParameterNotResolved => duckdb_error_type_DUCKDB_ERROR_PARAMETER_NOT_RESOLVED,
191 Self::ParameterNotAllowed => duckdb_error_type_DUCKDB_ERROR_PARAMETER_NOT_ALLOWED,
192 Self::Dependency => duckdb_error_type_DUCKDB_ERROR_DEPENDENCY,
193 Self::Http => duckdb_error_type_DUCKDB_ERROR_HTTP,
194 Self::MissingExtension => duckdb_error_type_DUCKDB_ERROR_MISSING_EXTENSION,
195 }
196 }
197
198 #[must_use]
201 pub(crate) const fn from_raw(raw: duckdb_error_type) -> Self {
202 match raw {
203 x if x == duckdb_error_type_DUCKDB_ERROR_OUT_OF_RANGE => Self::OutOfRange,
204 x if x == duckdb_error_type_DUCKDB_ERROR_CONVERSION => Self::Conversion,
205 x if x == duckdb_error_type_DUCKDB_ERROR_UNKNOWN_TYPE => Self::UnknownType,
206 x if x == duckdb_error_type_DUCKDB_ERROR_DECIMAL => Self::Decimal,
207 x if x == duckdb_error_type_DUCKDB_ERROR_MISMATCH_TYPE => Self::MismatchType,
208 x if x == duckdb_error_type_DUCKDB_ERROR_DIVIDE_BY_ZERO => Self::DivideByZero,
209 x if x == duckdb_error_type_DUCKDB_ERROR_OBJECT_SIZE => Self::ObjectSize,
210 x if x == duckdb_error_type_DUCKDB_ERROR_INVALID_TYPE => Self::InvalidType,
211 x if x == duckdb_error_type_DUCKDB_ERROR_SERIALIZATION => Self::Serialization,
212 x if x == duckdb_error_type_DUCKDB_ERROR_TRANSACTION => Self::Transaction,
213 x if x == duckdb_error_type_DUCKDB_ERROR_NOT_IMPLEMENTED => Self::NotImplemented,
214 x if x == duckdb_error_type_DUCKDB_ERROR_EXPRESSION => Self::Expression,
215 x if x == duckdb_error_type_DUCKDB_ERROR_CATALOG => Self::Catalog,
216 x if x == duckdb_error_type_DUCKDB_ERROR_PARSER => Self::Parser,
217 x if x == duckdb_error_type_DUCKDB_ERROR_PLANNER => Self::Planner,
218 x if x == duckdb_error_type_DUCKDB_ERROR_SCHEDULER => Self::Scheduler,
219 x if x == duckdb_error_type_DUCKDB_ERROR_EXECUTOR => Self::Executor,
220 x if x == duckdb_error_type_DUCKDB_ERROR_CONSTRAINT => Self::Constraint,
221 x if x == duckdb_error_type_DUCKDB_ERROR_INDEX => Self::Index,
222 x if x == duckdb_error_type_DUCKDB_ERROR_STAT => Self::Stat,
223 x if x == duckdb_error_type_DUCKDB_ERROR_CONNECTION => Self::Connection,
224 x if x == duckdb_error_type_DUCKDB_ERROR_SYNTAX => Self::Syntax,
225 x if x == duckdb_error_type_DUCKDB_ERROR_SETTINGS => Self::Settings,
226 x if x == duckdb_error_type_DUCKDB_ERROR_BINDER => Self::Binder,
227 x if x == duckdb_error_type_DUCKDB_ERROR_NETWORK => Self::Network,
228 x if x == duckdb_error_type_DUCKDB_ERROR_OPTIMIZER => Self::Optimizer,
229 x if x == duckdb_error_type_DUCKDB_ERROR_NULL_POINTER => Self::NullPointer,
230 x if x == duckdb_error_type_DUCKDB_ERROR_IO => Self::Io,
231 x if x == duckdb_error_type_DUCKDB_ERROR_INTERRUPT => Self::Interrupt,
232 x if x == duckdb_error_type_DUCKDB_ERROR_FATAL => Self::Fatal,
233 x if x == duckdb_error_type_DUCKDB_ERROR_INTERNAL => Self::Internal,
234 x if x == duckdb_error_type_DUCKDB_ERROR_INVALID_INPUT => Self::InvalidInput,
235 x if x == duckdb_error_type_DUCKDB_ERROR_OUT_OF_MEMORY => Self::OutOfMemory,
236 x if x == duckdb_error_type_DUCKDB_ERROR_PERMISSION => Self::Permission,
237 x if x == duckdb_error_type_DUCKDB_ERROR_PARAMETER_NOT_RESOLVED => {
238 Self::ParameterNotResolved
239 }
240 x if x == duckdb_error_type_DUCKDB_ERROR_PARAMETER_NOT_ALLOWED => {
241 Self::ParameterNotAllowed
242 }
243 x if x == duckdb_error_type_DUCKDB_ERROR_DEPENDENCY => Self::Dependency,
244 x if x == duckdb_error_type_DUCKDB_ERROR_HTTP => Self::Http,
245 x if x == duckdb_error_type_DUCKDB_ERROR_MISSING_EXTENSION => Self::MissingExtension,
246 _ => Self::Invalid,
247 }
248 }
249
250 #[must_use]
252 pub const fn as_str(self) -> &'static str {
253 match self {
254 Self::Invalid => "invalid",
255 Self::OutOfRange => "out of range",
256 Self::Conversion => "conversion",
257 Self::UnknownType => "unknown type",
258 Self::Decimal => "decimal",
259 Self::MismatchType => "type mismatch",
260 Self::DivideByZero => "divide by zero",
261 Self::ObjectSize => "object size",
262 Self::InvalidType => "invalid type",
263 Self::Serialization => "serialization",
264 Self::Transaction => "transaction",
265 Self::NotImplemented => "not implemented",
266 Self::Expression => "expression",
267 Self::Catalog => "catalog",
268 Self::Parser => "parser",
269 Self::Planner => "planner",
270 Self::Scheduler => "scheduler",
271 Self::Executor => "executor",
272 Self::Constraint => "constraint",
273 Self::Index => "index",
274 Self::Stat => "statistics",
275 Self::Connection => "connection",
276 Self::Syntax => "syntax",
277 Self::Settings => "settings",
278 Self::Binder => "binder",
279 Self::Network => "network",
280 Self::Optimizer => "optimizer",
281 Self::NullPointer => "null pointer",
282 Self::Io => "I/O",
283 Self::Interrupt => "interrupt",
284 Self::Fatal => "fatal",
285 Self::Internal => "internal",
286 Self::InvalidInput => "invalid input",
287 Self::OutOfMemory => "out of memory",
288 Self::Permission => "permission",
289 Self::ParameterNotResolved => "parameter not resolved",
290 Self::ParameterNotAllowed => "parameter not allowed",
291 Self::Dependency => "dependency",
292 Self::Http => "HTTP",
293 Self::MissingExtension => "missing extension",
294 }
295 }
296}
297
298impl fmt::Display for DuckDbErrorType {
299 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
300 f.write_str(self.as_str())
301 }
302}
303
304pub struct ErrorData {
314 raw: duckdb_error_data,
315}
316
317impl ErrorData {
318 #[must_use]
322 pub fn new(error_type: DuckDbErrorType, message: &str) -> Self {
323 let c_msg = CString::new(message).unwrap_or_else(|_| {
324 let pos = message
325 .bytes()
326 .position(|b| b == 0)
327 .unwrap_or(message.len());
328 CString::new(&message.as_bytes()[..pos]).unwrap_or_default()
329 });
330 let raw = unsafe { duckdb_create_error_data(error_type.to_raw(), c_msg.as_ptr()) };
333 Self { raw }
334 }
335
336 #[inline]
343 #[must_use]
344 pub const unsafe fn from_raw(raw: duckdb_error_data) -> Self {
345 Self { raw }
346 }
347
348 #[inline]
350 #[must_use]
351 pub const fn as_raw(&self) -> duckdb_error_data {
352 self.raw
353 }
354
355 #[inline]
357 #[must_use]
358 pub const fn is_null(&self) -> bool {
359 self.raw.is_null()
360 }
361
362 #[must_use]
364 pub fn has_error(&self) -> bool {
365 if self.raw.is_null() {
366 return false;
367 }
368 unsafe { duckdb_error_data_has_error(self.raw) }
370 }
371
372 #[must_use]
374 pub fn error_type(&self) -> DuckDbErrorType {
375 if self.raw.is_null() {
376 return DuckDbErrorType::Invalid;
377 }
378 let raw = unsafe { duckdb_error_data_error_type(self.raw) };
380 DuckDbErrorType::from_raw(raw)
381 }
382
383 #[must_use]
388 pub fn message(&self) -> Option<String> {
389 if self.raw.is_null() {
390 return None;
391 }
392 let ptr = unsafe { duckdb_error_data_message(self.raw) };
395 if ptr.is_null() {
396 return None;
397 }
398 unsafe { CStr::from_ptr(ptr) }
400 .to_str()
401 .ok()
402 .map(String::from)
403 }
404
405 #[must_use]
409 pub fn into_extension_error(self) -> ExtensionError {
410 let msg = self
411 .message()
412 .unwrap_or_else(|| "unknown DuckDB error".to_owned());
413 ExtensionError::new(msg)
414 }
415
416 #[inline]
420 #[must_use]
421 pub const fn into_raw(self) -> duckdb_error_data {
422 let raw = self.raw;
423 std::mem::forget(self);
424 raw
425 }
426}
427
428impl Drop for ErrorData {
429 fn drop(&mut self) {
430 if !self.raw.is_null() {
431 unsafe { duckdb_destroy_error_data(&raw mut self.raw) };
433 }
434 }
435}
436
437impl fmt::Debug for ErrorData {
438 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
439 f.debug_struct("ErrorData")
440 .field("error_type", &self.error_type())
441 .field("message", &self.message())
442 .finish()
443 }
444}
445
446impl fmt::Display for ErrorData {
447 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
448 match self.message() {
449 Some(msg) => write!(f, "{}: {msg}", self.error_type()),
450 None => f.write_str(self.error_type().as_str()),
451 }
452 }
453}
454
455impl std::error::Error for ErrorData {}
456
457impl From<ErrorData> for ExtensionError {
458 #[inline]
459 fn from(err: ErrorData) -> Self {
460 err.into_extension_error()
461 }
462}
463
464pub fn check_valid_utf8(bytes: &[u8]) -> Result<(), ErrorData> {
475 let len = idx_t::try_from(bytes.len()).unwrap_or(idx_t::MAX);
476 let raw = unsafe { duckdb_valid_utf8_check(bytes.as_ptr().cast(), len) };
478 let err = unsafe { ErrorData::from_raw(raw) };
480 if err.has_error() {
481 Err(err)
482 } else {
483 Ok(())
484 }
485}
486
487#[cfg(test)]
488mod tests {
489 use super::*;
490
491 const ALL_VARIANTS: [DuckDbErrorType; 40] = [
492 DuckDbErrorType::Invalid,
493 DuckDbErrorType::OutOfRange,
494 DuckDbErrorType::Conversion,
495 DuckDbErrorType::UnknownType,
496 DuckDbErrorType::Decimal,
497 DuckDbErrorType::MismatchType,
498 DuckDbErrorType::DivideByZero,
499 DuckDbErrorType::ObjectSize,
500 DuckDbErrorType::InvalidType,
501 DuckDbErrorType::Serialization,
502 DuckDbErrorType::Transaction,
503 DuckDbErrorType::NotImplemented,
504 DuckDbErrorType::Expression,
505 DuckDbErrorType::Catalog,
506 DuckDbErrorType::Parser,
507 DuckDbErrorType::Planner,
508 DuckDbErrorType::Scheduler,
509 DuckDbErrorType::Executor,
510 DuckDbErrorType::Constraint,
511 DuckDbErrorType::Index,
512 DuckDbErrorType::Stat,
513 DuckDbErrorType::Connection,
514 DuckDbErrorType::Syntax,
515 DuckDbErrorType::Settings,
516 DuckDbErrorType::Binder,
517 DuckDbErrorType::Network,
518 DuckDbErrorType::Optimizer,
519 DuckDbErrorType::NullPointer,
520 DuckDbErrorType::Io,
521 DuckDbErrorType::Interrupt,
522 DuckDbErrorType::Fatal,
523 DuckDbErrorType::Internal,
524 DuckDbErrorType::InvalidInput,
525 DuckDbErrorType::OutOfMemory,
526 DuckDbErrorType::Permission,
527 DuckDbErrorType::ParameterNotResolved,
528 DuckDbErrorType::ParameterNotAllowed,
529 DuckDbErrorType::Dependency,
530 DuckDbErrorType::Http,
531 DuckDbErrorType::MissingExtension,
532 ];
533
534 #[test]
535 fn error_type_round_trip_all_variants() {
536 for variant in ALL_VARIANTS {
537 let raw = variant.to_raw();
538 assert_eq!(
539 DuckDbErrorType::from_raw(raw),
540 variant,
541 "round-trip failed for {variant:?}"
542 );
543 }
544 }
545
546 #[test]
547 fn error_type_unknown_raw_maps_to_invalid() {
548 assert_eq!(DuckDbErrorType::from_raw(9999), DuckDbErrorType::Invalid);
549 }
550
551 #[test]
552 fn error_type_distinct_raw_values() {
553 for (i, a) in ALL_VARIANTS.iter().enumerate() {
554 for b in ALL_VARIANTS.iter().skip(i + 1) {
555 assert_ne!(
556 a.to_raw(),
557 b.to_raw(),
558 "variants {a:?} and {b:?} share a raw value"
559 );
560 }
561 }
562 }
563
564 #[test]
565 fn null_error_data_has_no_error() {
566 let err = unsafe { ErrorData::from_raw(std::ptr::null_mut()) };
567 assert!(err.is_null());
568 assert!(!err.has_error());
569 assert_eq!(err.error_type(), DuckDbErrorType::Invalid);
570 assert!(err.message().is_none());
571 }
572
573 #[test]
574 fn into_raw_forgets_handle() {
575 let err = unsafe { ErrorData::from_raw(std::ptr::null_mut()) };
576 let raw = err.into_raw();
577 assert!(raw.is_null());
578 }
579
580 #[test]
581 fn error_type_display_matches_as_str() {
582 for variant in ALL_VARIANTS {
583 assert!(!variant.as_str().is_empty(), "{variant:?} has empty label");
584 assert_eq!(format!("{variant}"), variant.as_str());
585 }
586 }
587
588 #[test]
589 fn error_type_labels_are_distinct() {
590 for (i, a) in ALL_VARIANTS.iter().enumerate() {
591 for b in ALL_VARIANTS.iter().skip(i + 1) {
592 assert_ne!(a.as_str(), b.as_str(), "{a:?} and {b:?} share a label");
593 }
594 }
595 }
596
597 #[test]
598 fn null_error_data_debug_and_display() {
599 let err = unsafe { ErrorData::from_raw(std::ptr::null_mut()) };
602 assert_eq!(err.to_string(), "invalid");
603 let dbg = format!("{err:?}");
604 assert!(dbg.contains("ErrorData"), "debug was: {dbg}");
605 assert!(dbg.contains("Invalid"), "debug was: {dbg}");
606 }
607
608 #[test]
609 fn from_error_data_for_extension_error() {
610 let err = unsafe { ErrorData::from_raw(std::ptr::null_mut()) };
611 let ext: ExtensionError = err.into();
612 assert_eq!(ext.as_str(), "unknown DuckDB error");
613 }
614
615 #[test]
616 fn error_data_usable_as_std_error() {
617 fn takes_error(_e: &dyn std::error::Error) {}
618 let err = unsafe { ErrorData::from_raw(std::ptr::null_mut()) };
619 takes_error(&err);
620 }
621}