oracle/
error.rs

1// Rust-oracle - Rust binding for Oracle database
2//
3// URL: https://github.com/kubo/rust-oracle
4//
5//-----------------------------------------------------------------------------
6// Copyright (c) 2017-2025 Kubo Takehiro <kubo@jiubao.org>. All rights reserved.
7// This program is free software: you can modify it and/or redistribute it
8// under the terms of:
9//
10// (i)  the Universal Permissive License v 1.0 or at your option, any
11//      later version (http://oss.oracle.com/licenses/upl); and/or
12//
13// (ii) the Apache License v 2.0. (http://www.apache.org/licenses/LICENSE-2.0)
14//-----------------------------------------------------------------------------
15
16use crate::to_rust_str;
17use crate::AssertSend;
18use crate::AssertSync;
19use crate::Context;
20#[cfg(doc)]
21use crate::{Batch, BatchBuilder, Connection, Statement};
22use odpic_sys::*;
23use std::borrow::Cow;
24use std::error;
25use std::ffi::CStr;
26use std::fmt;
27#[cfg(feature = "struct_error")]
28use std::fmt::Display;
29use std::mem::MaybeUninit;
30use std::num;
31use std::str;
32use std::sync;
33
34// DPI-1010: not connected
35pub(crate) const DPI_ERR_NOT_CONNECTED: i32 = 1010;
36
37// DPI-1019: buffer size of %u is too small
38pub(crate) const DPI_ERR_BUFFER_SIZE_TOO_SMALL: i32 = 1019;
39
40#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
41#[non_exhaustive]
42/// A list of error categories.
43///
44/// It is used with the [`Error`] type.
45///
46/// Use `_` to match “all other errors” in `match` expression because it has [`#[non_exhaustive]`](https://doc.rust-lang.org/reference/attributes/type_system.html#the-non_exhaustive-attribute) attribute.
47pub enum ErrorKind {
48    /// Error from an underlying Oracle client library.
49    OciError,
50
51    /// Error from an underlying ODPI-C layer.
52    DpiError,
53
54    /// Error when NULL value is got but the target rust type cannot handle NULL.
55    /// Use `Option<...>` in this case.
56    NullValue,
57
58    /// Error when conversion from a string to an Oracle value fails
59    ParseError,
60
61    /// Error when conversion from a type to another fails due to out-of-range
62    OutOfRange,
63
64    /// Error when an unacceptable argument is passed
65    InvalidArgument,
66
67    /// Error when conversion from a type to another is not allowed.
68    InvalidTypeConversion,
69
70    /// Error when the bind parameter index is out of range. (one based)
71    InvalidBindIndex,
72
73    /// Error when the bind parameter name is not in the SQL.
74    InvalidBindName,
75
76    /// Error when the column index is out of range. (zero based)
77    InvalidColumnIndex,
78
79    /// Error when the column name is not in the SQL.
80    InvalidColumnName,
81
82    /// Error when the specified attribute name is not found.
83    InvalidAttributeName,
84
85    /// Error when invalid method is called such as calling execute for select statements.
86    InvalidOperation,
87
88    /// Error when an uninitialized bind value is accessed. Bind values
89    /// must be initialized by [`Statement::bind`], [`Statement::execute`]
90    /// or [`Connection::execute`] in advance.
91    UninitializedBindValue,
92
93    /// Error when no more rows exist in the SQL.
94    NoDataFound,
95
96    /// Error when [`BatchBuilder::with_batch_errors`] is set and [`Batch::execute`]
97    /// fails by supplied batch data.
98    /// See ["Error Handling with batch errors"](Batch#error-handling-with-batch-errors)
99    BatchErrors,
100
101    /// Internal error. When you get this error, please report it with a test case to reproduce it.
102    InternalError,
103
104    /// A custom error that does not match any other error kind.
105    ///
106    /// This is intended to be used outside of this crate.
107    Other,
108}
109
110/// The error type for oracle
111#[derive(Debug)]
112#[cfg(feature = "struct_error")]
113pub struct Error {
114    kind: ErrorKind,
115    message: Cow<'static, str>,
116    // DbError is in Box to reduce the size of this struct.
117    // See: https://rust-lang.github.io/rust-clippy/master/index.html#result_large_err
118    dberr: Option<Box<DbError>>,
119    batch_errors: Option<Vec<DbError>>,
120    source: Option<Box<dyn error::Error + Send + Sync>>,
121}
122
123/// The error type for oracle
124///
125/// **Note:** This enum will be changed to struct in the future.
126#[non_exhaustive]
127#[derive(Debug)]
128#[cfg(not(feature = "struct_error"))]
129pub enum Error {
130    /// Error from an underlying Oracle client library.
131    #[deprecated(note = "Use kind() to check the error category. Use db_error() to get DbError.")]
132    OciError(DbError),
133
134    /// Error from an underlying ODPI-C layer.
135    #[deprecated(note = "Use kind() to check the error category. Use db_error() to get DbError.")]
136    DpiError(DbError),
137
138    /// Error when NULL value is got but the target rust type cannot handle NULL.
139    /// Use `Option<...>` in this case.
140    #[deprecated(note = "Use kind() to check the error category.")]
141    NullValue,
142
143    /// Error when conversion from a string to an Oracle value fails
144    #[deprecated(
145        note = "Use kind() to check the error category. Use source() to get the underlying error."
146    )]
147    ParseError(Box<dyn error::Error + Send + Sync>),
148
149    /// Error when conversion from a type to another fails due to out-of-range
150    #[deprecated(
151        note = "Use kind() to check the error category. Use to_string() to get the message."
152    )]
153    OutOfRange(String),
154
155    /// Error when conversion from a type to another is not allowed.
156    #[deprecated(note = "Use kind() to check the error category.")]
157    InvalidTypeConversion(String, String),
158
159    /// Error when an unacceptable argument is passed
160    #[deprecated]
161    InvalidArgument {
162        message: Cow<'static, str>,
163        source: Option<Box<dyn error::Error + Send + Sync>>,
164    },
165
166    /// Error when the bind parameter index is out of range. (one based)
167    #[deprecated(note = "Use kind() to check the error category.")]
168    InvalidBindIndex(usize),
169
170    /// Error when the bind parameter name is not in the SQL.
171    #[deprecated(note = "Use kind() to check the error category.")]
172    InvalidBindName(String),
173
174    /// Error when the column index is out of range. (zero based)
175    #[deprecated(note = "Use kind() to check the error category.")]
176    InvalidColumnIndex(usize),
177
178    /// Error when the column name is not in the SQL.
179    #[deprecated(note = "Use kind() to check the error category.")]
180    InvalidColumnName(String),
181
182    /// Error when the specified attribute name is not found.
183    #[deprecated(note = "Use kind() to check the error category.")]
184    InvalidAttributeName(String),
185
186    /// Error when invalid method is called such as calling execute for select statements.
187    #[deprecated(
188        note = "Use kind() to check the error category. Use to_string() to get the message."
189    )]
190    InvalidOperation(String),
191
192    /// Error when an uninitialized bind value is accessed. Bind values
193    /// must be initialized by [`Statement::bind`], [`Statement::execute`]
194    /// or [`Connection::execute`] in advance.
195    #[deprecated(note = "Use kind() to check the error category.")]
196    UninitializedBindValue,
197
198    /// Error when no more rows exist in the SQL.
199    #[deprecated(note = "Use kind() to check the error category.")]
200    NoDataFound,
201
202    #[deprecated(
203        note = "Use kind() to check the error category. Use batch_errors() to get the db errors."
204    )]
205    BatchErrors(Vec<DbError>),
206
207    /// Internal error. When you get this error, please report it with a test case to reproduce it.
208    #[deprecated(
209        note = "Use kind() to check the error category. Use to_string() to get the message."
210    )]
211    InternalError(String),
212
213    /// Other or newly added error
214    ///
215    /// Don't use this variant directly.
216    #[non_exhaustive]
217    Other {
218        kind: ErrorKind,
219        message: Cow<'static, str>,
220        source: Option<Box<dyn error::Error + Send + Sync>>,
221    },
222}
223
224impl Error {
225    pub(crate) fn from_context(ctxt: &Context) -> Error {
226        let err = unsafe {
227            let mut err = MaybeUninit::uninit();
228            dpiContext_getError(ctxt.context, err.as_mut_ptr());
229            err.assume_init()
230        };
231        Error::from_dpi_error(&err)
232    }
233
234    pub(crate) fn from_dpi_error(err: &dpiErrorInfo) -> Error {
235        Error::from_db_error(DbError::from_dpi_error(err))
236    }
237}
238
239#[cfg(feature = "struct_error")]
240impl Error {
241    /// Create a new error.
242    pub fn new<M>(kind: ErrorKind, message: M) -> Error
243    where
244        M: Into<Cow<'static, str>>,
245    {
246        Error {
247            kind,
248            message: message.into(),
249            dberr: None,
250            batch_errors: None,
251            source: None,
252        }
253    }
254
255    pub(crate) fn add_dberr(self, dberr: DbError) -> Error {
256        Error {
257            dberr: Some(Box::new(dberr)),
258            ..self
259        }
260    }
261
262    pub(crate) fn add_batch_errors(self, batch_errors: Vec<DbError>) -> Error {
263        Error {
264            batch_errors: Some(batch_errors),
265            ..self
266        }
267    }
268
269    /// Add the lower-level error used as the return value of [`Error::source()`].
270    ///
271    /// **Note:** This is intended to be applied to the Error created by [`Error::new()`].
272    /// Otherwise, `source` may be ignored.
273    ///
274    /// [`Error::source()`]: https://doc.rust-lang.org/std/error/trait.Error.html#method.source
275    pub fn add_source<E>(self, source: E) -> Error
276    where
277        E: Into<Box<dyn error::Error + Send + Sync>>,
278    {
279        Error {
280            source: Some(source.into()),
281            ..self
282        }
283    }
284
285    /// Create a new error from an arbitrary error
286    ///
287    /// The error message of this error is same with that of the source specified.
288    ///
289    /// This is a shortcut for `Error::new(kind, "").add_source(source)`.
290    pub fn with_source<E>(kind: ErrorKind, source: E) -> Error
291    where
292        E: Into<Box<dyn error::Error + Send + Sync>>,
293    {
294        Error::new(kind, "").add_source(source)
295    }
296
297    pub(crate) fn from_db_error(dberr: DbError) -> Error {
298        let (kind, message_prefix) = if dberr.message().starts_with("DPI") {
299            (ErrorKind::DpiError, "DPI")
300        } else {
301            (ErrorKind::OciError, "OCI")
302        };
303        Error::new(kind, format!("{} Error: {}", message_prefix, dberr.message)).add_dberr(dberr)
304    }
305
306    pub fn kind(&self) -> ErrorKind {
307        self.kind
308    }
309
310    /// Returns [`DbError`].
311    pub fn db_error(&self) -> Option<&DbError> {
312        self.dberr.as_ref().map(|b| b.as_ref())
313    }
314
315    /// Returns batch errors.
316    /// See ["Error Handling with batch errors"](Batch#error-handling-with-batch-errors)
317    pub fn batch_errors(&self) -> Option<&Vec<DbError>> {
318        self.batch_errors.as_ref()
319    }
320
321    /// Returns Oracle error code.
322    /// For example 1 for "ORA-0001: unique constraint violated"
323    pub fn oci_code(&self) -> Option<i32> {
324        match (self.kind, &self.dberr) {
325            (ErrorKind::OciError, Some(dberr)) if dberr.code != 0 => Some(dberr.code),
326            _ => None,
327        }
328    }
329
330    /// Returns [ODPI-C](https://oracle.github.io/odpi/) error code.
331    pub fn dpi_code(&self) -> Option<i32> {
332        match (self.kind, &self.dberr) {
333            (ErrorKind::DpiError, Some(dberr)) => dpi_error_in_message(&dberr.message),
334            _ => None,
335        }
336    }
337
338    /// Consumes the Error, returning its inner error (if any).
339    pub fn into_source(self) -> Option<Box<dyn error::Error + Send + Sync>> {
340        self.source
341    }
342
343    pub(crate) fn oci_error(dberr: DbError) -> Error {
344        Error::new(ErrorKind::OciError, format!("OCI Error: {}", dberr.message)).add_dberr(dberr)
345    }
346
347    pub(crate) fn null_value() -> Error {
348        Error::new(ErrorKind::NullValue, "NULL value found")
349    }
350
351    pub(crate) fn parse_error<T>(source: T) -> Error
352    where
353        T: Into<Box<dyn error::Error + Send + Sync>>,
354    {
355        let source = source.into();
356        Error::new(ErrorKind::ParseError, "").add_source(source)
357    }
358
359    pub(crate) fn out_of_range<T>(message: T) -> Error
360    where
361        T: Into<Cow<'static, str>>,
362    {
363        Error::new(ErrorKind::OutOfRange, message.into())
364    }
365
366    pub(crate) fn invalid_type_conversion<T1, T2>(from: T1, to: T2) -> Error
367    where
368        T1: Display,
369        T2: Display,
370    {
371        Error::new(
372            ErrorKind::InvalidTypeConversion,
373            format!("invalid type conversion from {} to {}", from, to),
374        )
375    }
376
377    pub(crate) fn invalid_bind_index<T>(index: T) -> Error
378    where
379        T: Display,
380    {
381        Error::new(
382            ErrorKind::InvalidBindIndex,
383            format!("invalid bind index {} (one-based)", index),
384        )
385    }
386
387    pub(crate) fn invalid_bind_name<T>(name: T) -> Error
388    where
389        T: Display,
390    {
391        Error::new(
392            ErrorKind::InvalidBindName,
393            format!("invalid bind name {}", name),
394        )
395    }
396
397    pub(crate) fn invalid_column_index<T>(index: T) -> Error
398    where
399        T: Display,
400    {
401        Error::new(
402            ErrorKind::InvalidColumnIndex,
403            format!("invalid column index {} (zero-based)", index),
404        )
405    }
406
407    pub(crate) fn invalid_column_name<T>(name: T) -> Error
408    where
409        T: Display,
410    {
411        Error::new(
412            ErrorKind::InvalidColumnName,
413            format!("invalid column name {}", name),
414        )
415    }
416
417    pub(crate) fn invalid_attribute_name<T>(name: T) -> Error
418    where
419        T: Display,
420    {
421        Error::new(
422            ErrorKind::InvalidAttributeName,
423            format!("invalid attribute name {}", name),
424        )
425    }
426
427    pub(crate) fn invalid_operation<T>(message: T) -> Error
428    where
429        T: Into<Cow<'static, str>>,
430    {
431        Error::new(ErrorKind::InvalidOperation, message.into())
432    }
433
434    pub(crate) fn uninitialized_bind_value() -> Error {
435        Error::new(
436            ErrorKind::UninitializedBindValue,
437            "try to access uninitialized bind value",
438        )
439    }
440
441    pub(crate) fn no_data_found() -> Error {
442        Error::new(ErrorKind::NoDataFound, "no data found")
443    }
444
445    pub(crate) fn make_batch_errors(batch_errors: Vec<DbError>) -> Error {
446        Error::new(
447            ErrorKind::BatchErrors,
448            format!("batch error containing {} error(s)", batch_errors.len()),
449        )
450        .add_batch_errors(batch_errors)
451    }
452
453    pub(crate) fn internal_error<T>(message: T) -> Error
454    where
455        T: Into<Cow<'static, str>>,
456    {
457        Error::new(ErrorKind::InternalError, message.into())
458    }
459
460    pub(crate) fn invalid_argument<M>(message: M) -> Error
461    where
462        M: Into<Cow<'static, str>>,
463    {
464        Error::new(ErrorKind::InvalidArgument, message.into())
465    }
466}
467
468#[allow(deprecated)]
469#[cfg(not(feature = "struct_error"))]
470impl Error {
471    /// Create a new error.
472    pub fn new<M>(kind: ErrorKind, message: M) -> Error
473    where
474        M: Into<Cow<'static, str>>,
475    {
476        Error::Other {
477            kind,
478            message: message.into(),
479            source: None,
480        }
481    }
482
483    pub(crate) fn from_db_error(dberr: DbError) -> Error {
484        if dberr.message().starts_with("DPI") {
485            Error::DpiError(dberr)
486        } else {
487            Error::OciError(dberr)
488        }
489    }
490
491    /// Add the lower-level error used as the return value of [`Error::source()`].
492    ///
493    /// **Note:** This is intended to be applied to the Error created by [`Error::new()`].
494    /// Otherwise, `source` may be ignored.
495    ///
496    /// [`Error::source()`]: https://doc.rust-lang.org/std/error/trait.Error.html#method.source
497    pub fn add_source<E>(self, source: E) -> Error
498    where
499        E: Into<Box<dyn error::Error + Send + Sync>>,
500    {
501        match self {
502            Error::InvalidArgument { message, .. } => Error::InvalidArgument {
503                message,
504                source: Some(source.into()),
505            },
506            Error::Other { kind, message, .. } => Error::Other {
507                kind,
508                message,
509                source: Some(source.into()),
510            },
511            _ => self,
512        }
513    }
514
515    /// Create a new error from an arbitrary error
516    ///
517    /// The error message of this error is same with that of the source specified.
518    ///
519    /// This is a shortcut for `Error::new(kind, "").add_source(source)`.
520    pub fn with_source<E>(kind: ErrorKind, source: E) -> Error
521    where
522        E: Into<Box<dyn error::Error + Send + Sync>>,
523    {
524        Error::new(kind, "").add_source(source)
525    }
526
527    /// Returns the corresponding [`ErrorKind`] for this error.
528    pub fn kind(&self) -> ErrorKind {
529        match self {
530            Error::OciError(_) => ErrorKind::OciError,
531            Error::DpiError(_) => ErrorKind::DpiError,
532            Error::NullValue => ErrorKind::NullValue,
533            Error::ParseError(_) => ErrorKind::ParseError,
534            Error::OutOfRange(_) => ErrorKind::OutOfRange,
535            Error::InvalidArgument { .. } => ErrorKind::InvalidArgument,
536            Error::InvalidTypeConversion(_, _) => ErrorKind::InvalidTypeConversion,
537            Error::InvalidBindIndex(_) => ErrorKind::InvalidBindIndex,
538            Error::InvalidBindName(_) => ErrorKind::InvalidBindName,
539            Error::InvalidColumnIndex(_) => ErrorKind::InvalidColumnIndex,
540            Error::InvalidColumnName(_) => ErrorKind::InvalidColumnName,
541            Error::InvalidAttributeName(_) => ErrorKind::InvalidAttributeName,
542            Error::InvalidOperation(_) => ErrorKind::InvalidOperation,
543            Error::UninitializedBindValue => ErrorKind::UninitializedBindValue,
544            Error::NoDataFound => ErrorKind::NoDataFound,
545            Error::BatchErrors(_) => ErrorKind::BatchErrors,
546            Error::InternalError(_) => ErrorKind::InternalError,
547            Error::Other { kind, .. } => *kind,
548        }
549    }
550
551    /// Returns [`DbError`].
552    pub fn db_error(&self) -> Option<&DbError> {
553        match self {
554            Error::OciError(err) | Error::DpiError(err) => Some(err),
555            _ => None,
556        }
557    }
558
559    /// Returns batch errors.
560    /// See ["Error Handling with batch errors"](Batch#error-handling-with-batch-errors)
561    pub fn batch_errors(&self) -> Option<&Vec<DbError>> {
562        match self {
563            Error::BatchErrors(errs) => Some(errs),
564            _ => None,
565        }
566    }
567
568    /// Returns Oracle error code.
569    /// For example 1 for "ORA-0001: unique constraint violated"
570    pub fn oci_code(&self) -> Option<i32> {
571        if let Error::OciError(dberr) = &self {
572            if dberr.code != 0 {
573                Some(dberr.code)
574            } else {
575                None
576            }
577        } else {
578            None
579        }
580    }
581
582    /// Returns [ODPI-C](https://oracle.github.io/odpi/) error code.
583    pub fn dpi_code(&self) -> Option<i32> {
584        if let Error::DpiError(dberr) = &self {
585            dpi_error_in_message(&dberr.message)
586        } else {
587            None
588        }
589    }
590
591    /// Consumes the Error, returning its inner error (if any).
592    pub fn into_source(self) -> Option<Box<dyn error::Error + Send + Sync>> {
593        match self {
594            Error::ParseError(err) => Some(err),
595            Error::InvalidArgument { source, .. } => source,
596            Error::Other { source, .. } => source,
597            _ => None,
598        }
599    }
600
601    pub(crate) fn oci_error(dberr: DbError) -> Error {
602        Error::OciError(dberr)
603    }
604
605    pub(crate) fn null_value() -> Error {
606        Error::NullValue
607    }
608
609    pub(crate) fn parse_error<T>(source: T) -> Error
610    where
611        T: Into<Box<dyn error::Error + Send + Sync>>,
612    {
613        Error::ParseError(source.into())
614    }
615
616    pub(crate) fn out_of_range<T>(message: T) -> Error
617    where
618        T: Into<String>,
619    {
620        Error::OutOfRange(message.into())
621    }
622
623    pub(crate) fn invalid_type_conversion<T1, T2>(from: T1, to: T2) -> Error
624    where
625        T1: Into<String>,
626        T2: Into<String>,
627    {
628        Error::InvalidTypeConversion(from.into(), to.into())
629    }
630
631    pub(crate) fn invalid_bind_index(index: usize) -> Error {
632        Error::InvalidBindIndex(index)
633    }
634
635    pub(crate) fn invalid_bind_name<T>(name: T) -> Error
636    where
637        T: Into<String>,
638    {
639        Error::InvalidBindName(name.into())
640    }
641
642    pub(crate) fn invalid_column_index(index: usize) -> Error {
643        Error::InvalidColumnIndex(index)
644    }
645
646    pub(crate) fn invalid_column_name<T>(name: T) -> Error
647    where
648        T: Into<String>,
649    {
650        Error::InvalidColumnName(name.into())
651    }
652
653    pub(crate) fn invalid_attribute_name<T>(name: T) -> Error
654    where
655        T: Into<String>,
656    {
657        Error::InvalidAttributeName(name.into())
658    }
659
660    pub(crate) fn invalid_operation<T>(message: T) -> Error
661    where
662        T: Into<String>,
663    {
664        Error::InvalidOperation(message.into())
665    }
666
667    pub(crate) fn uninitialized_bind_value() -> Error {
668        Error::UninitializedBindValue
669    }
670
671    pub(crate) fn no_data_found() -> Error {
672        Error::NoDataFound
673    }
674
675    pub(crate) fn make_batch_errors(errs: Vec<DbError>) -> Error {
676        Error::BatchErrors(errs)
677    }
678
679    pub(crate) fn internal_error<T>(message: T) -> Error
680    where
681        T: Into<String>,
682    {
683        Error::InternalError(message.into())
684    }
685
686    pub(crate) fn invalid_argument<M>(message: M) -> Error
687    where
688        M: Into<Cow<'static, str>>,
689    {
690        Error::InvalidArgument {
691            message: message.into(),
692            source: None,
693        }
694    }
695}
696
697impl AssertSend for Error {}
698impl AssertSync for Error {}
699
700/// An error when parsing a string into an Oracle type fails.
701/// This appears only in boxed data associated with [`Error::ParseError`].
702#[derive(Eq, PartialEq, Clone)]
703pub struct ParseOracleTypeError {
704    typename: &'static str,
705}
706
707impl ParseOracleTypeError {
708    pub fn new(typename: &'static str) -> ParseOracleTypeError {
709        ParseOracleTypeError { typename }
710    }
711}
712
713impl fmt::Display for ParseOracleTypeError {
714    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
715        write!(f, "{} parse error", self.typename)
716    }
717}
718
719impl fmt::Debug for ParseOracleTypeError {
720    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
721        write!(f, "ParseOracleTypeError")
722    }
723}
724
725impl error::Error for ParseOracleTypeError {
726    fn description(&self) -> &str {
727        "Oracle type parse error"
728    }
729
730    fn cause(&self) -> Option<&dyn error::Error> {
731        None
732    }
733}
734
735/// Oracle database error or ODPI-C error
736#[derive(Debug, Eq, PartialEq, Clone)]
737pub struct DbError {
738    code: i32,
739    offset: u32,
740    message: String,
741    fn_name: Cow<'static, str>,
742    action: Cow<'static, str>,
743    is_recoverable: bool,
744    is_warning: bool,
745}
746
747impl DbError {
748    pub(crate) fn from_dpi_error(err: &dpiErrorInfo) -> DbError {
749        DbError {
750            code: err.code,
751            offset: err.offset,
752            message: to_rust_str(err.message, err.messageLength),
753            fn_name: unsafe { CStr::from_ptr(err.fnName) }.to_string_lossy(),
754            action: unsafe { CStr::from_ptr(err.action) }.to_string_lossy(),
755            is_recoverable: err.isRecoverable != 0,
756            is_warning: err.isWarning != 0,
757        }
758    }
759
760    pub(crate) fn to_warning(ctxt: &Context) -> Option<DbError> {
761        let err = unsafe {
762            let mut err = MaybeUninit::uninit();
763            dpiContext_getError(ctxt.context, err.as_mut_ptr());
764            err.assume_init()
765        };
766        if err.isWarning != 0 {
767            Some(DbError::from_dpi_error(&err))
768        } else {
769            None
770        }
771    }
772
773    /// Creates a new DbError. Note that its `is_recoverable` and `is_warning` values are always `false`.
774    pub fn new<M, F, A>(code: i32, offset: u32, message: M, fn_name: F, action: A) -> DbError
775    where
776        M: Into<String>,
777        F: Into<Cow<'static, str>>,
778        A: Into<Cow<'static, str>>,
779    {
780        DbError {
781            code,
782            offset,
783            message: message.into(),
784            fn_name: fn_name.into(),
785            action: action.into(),
786            is_recoverable: false,
787            is_warning: false,
788        }
789    }
790
791    /// The OCI error code if an OCI error has taken place. If no OCI error has taken place the value is 0.
792    pub fn code(&self) -> i32 {
793        self.code
794    }
795
796    /// The parse error offset (in bytes) when executing a statement or the row offset when performing bulk operations or fetching batch error information. If neither of these cases are true, the value is 0.
797    pub fn offset(&self) -> u32 {
798        self.offset
799    }
800
801    /// The error message
802    pub fn message(&self) -> &str {
803        &self.message
804    }
805
806    /// The public ODPI-C, used by rust-oracle, function name which was called in which the error took place.
807    pub fn fn_name(&self) -> &str {
808        &self.fn_name
809    }
810
811    /// The internal action that was being performed when the error took place.
812    pub fn action(&self) -> &str {
813        &self.action
814    }
815
816    /// A boolean value indicating if the error is recoverable. This always retruns `false` unless both client and server are at release 12.1 or higher.
817    pub fn is_recoverable(&self) -> bool {
818        self.is_recoverable
819    }
820
821    /// A boolean value indicating if the error information is for a warning returned by Oracle that does not prevent the requested operation from proceeding. Examples include connecting to the database with a password that is about to expire (within the grace period) and creating a stored procedure with compilation errors.
822    ///
823    /// See also [`Connection::last_warning`].
824    pub fn is_warning(&self) -> bool {
825        self.is_warning
826    }
827}
828
829#[cfg(feature = "struct_error")]
830impl fmt::Display for Error {
831    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
832        if !self.message.is_empty() {
833            write!(f, "{}", self.message)
834        } else if let Some(source) = &self.source {
835            write!(f, "{}", source)
836        } else {
837            write!(f, "blank error message")
838        }
839    }
840}
841
842#[cfg(not(feature = "struct_error"))]
843impl fmt::Display for Error {
844    #[allow(deprecated)]
845    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
846        match self {
847            Error::OciError(err) => write!(f, "OCI Error: {}", err.message),
848            Error::DpiError(err) => write!(f, "DPI Error: {}", err.message),
849            Error::NullValue => write!(f, "NULL value found"),
850            Error::ParseError(err) => write!(f, "{}", err),
851            Error::OutOfRange(msg) => write!(f, "{}", msg),
852            Error::InvalidTypeConversion(from, to) => {
853                write!(f, "invalid type conversion from {} to {}", from, to)
854            }
855            Error::InvalidBindIndex(idx) => {
856                write!(f, "invalid bind index {} (one-based)", idx)
857            }
858            Error::InvalidBindName(name) => write!(f, "invalid bind name {}", name),
859            Error::InvalidColumnIndex(idx) => {
860                write!(f, "invalid column index {} (zero-based)", idx)
861            }
862            Error::InvalidColumnName(name) => write!(f, "invalid column name {}", name),
863            Error::InvalidAttributeName(name) => write!(f, "invalid attribute name {}", name),
864            Error::InvalidOperation(msg) => write!(f, "{}", msg),
865            Error::UninitializedBindValue => write!(f, "try to access uninitialized bind value"),
866            Error::NoDataFound => write!(f, "no data found"),
867            Error::BatchErrors(errs) => {
868                write!(f, "batch errors (")?;
869                for err in errs {
870                    write!(f, "{}, ", err)?;
871                }
872                write!(f, ")")
873            }
874            Error::InternalError(msg) => write!(f, "{}", msg),
875            Error::InvalidArgument { message, .. } => write!(f, "{}", message),
876            Error::Other {
877                message, source, ..
878            } => {
879                if !message.is_empty() {
880                    write!(f, "{}", message)
881                } else if let Some(source) = source {
882                    write!(f, "{}", source)
883                } else {
884                    write!(f, "blank error message")
885                }
886            }
887        }
888    }
889}
890
891#[cfg(feature = "struct_error")]
892impl error::Error for Error {
893    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
894        if let Some(ref err) = self.source {
895            Some(err.as_ref())
896        } else {
897            None
898        }
899    }
900}
901
902#[cfg(not(feature = "struct_error"))]
903impl error::Error for Error {
904    #[allow(deprecated)]
905    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
906        match self {
907            Error::ParseError(err) => Some(err.as_ref()),
908            Error::InvalidArgument {
909                source: Some(source),
910                ..
911            } => Some(source.as_ref()),
912            Error::Other {
913                source: Some(source),
914                ..
915            } => Some(source.as_ref()),
916            _ => None,
917        }
918    }
919}
920
921impl fmt::Display for DbError {
922    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
923        write!(f, "{}", self.message)
924    }
925}
926
927impl From<ParseOracleTypeError> for Error {
928    fn from(err: ParseOracleTypeError) -> Self {
929        Error::parse_error(err)
930    }
931}
932
933impl From<num::ParseIntError> for Error {
934    fn from(err: num::ParseIntError) -> Self {
935        Error::parse_error(err)
936    }
937}
938
939impl From<num::ParseFloatError> for Error {
940    fn from(err: num::ParseFloatError) -> Self {
941        Error::parse_error(err)
942    }
943}
944
945impl From<num::TryFromIntError> for Error {
946    fn from(err: num::TryFromIntError) -> Self {
947        Error::parse_error(err)
948    }
949}
950
951impl From<str::Utf8Error> for Error {
952    fn from(err: str::Utf8Error) -> Self {
953        Error::parse_error(err)
954    }
955}
956
957impl<T> From<sync::PoisonError<T>> for Error {
958    fn from(err: sync::PoisonError<T>) -> Self {
959        Error::internal_error(err.to_string())
960    }
961}
962
963fn dpi_error_in_message(message: &str) -> Option<i32> {
964    let bytes = message.as_bytes();
965    if !bytes.starts_with(b"DPI-") {
966        return None;
967    }
968    let mut code = 0;
969    for c in bytes.iter().skip(4) {
970        if b'0' <= *c && *c <= b'9' {
971            code *= 10;
972            code += (*c - b'0') as i32;
973        } else if *c == b':' {
974            return Some(code);
975        } else {
976            break;
977        }
978    }
979    None
980}
981
982#[macro_export]
983#[doc(hidden)]
984macro_rules! chkerr {
985    ($ctxt:expr, $code:expr) => {{
986        #[allow(unused_unsafe)]
987        if unsafe { $code } != DPI_SUCCESS as i32 {
988            return Err($crate::Error::from_context($ctxt));
989        }
990    }};
991    ($ctxt:expr, $code:expr, $cleanup:stmt) => {{
992        #[allow(unused_unsafe)]
993        if unsafe { $code } != DPI_SUCCESS as i32 {
994            let err = $crate::Error::from_context($ctxt);
995            $cleanup
996            return Err(err);
997        }
998    }};
999}
1000
1001#[cfg(test)]
1002mod tests {
1003    use super::*;
1004    use std::error::Error as StdError;
1005    use std::io;
1006
1007    #[test]
1008    fn test_dpi_error_in_message() {
1009        assert_eq!(None, dpi_error_in_message("ORA-1234"));
1010        assert_eq!(None, dpi_error_in_message("DPI-1234"));
1011        assert_eq!(Some(1234), dpi_error_in_message("DPI-1234: xxx"));
1012    }
1013
1014    #[test]
1015    fn new_and_add_source() {
1016        let err = Error::new(ErrorKind::Other, "custom error");
1017        assert_eq!(err.kind(), ErrorKind::Other);
1018        assert_eq!(err.to_string(), "custom error");
1019        assert!(err.source().is_none());
1020
1021        let err = err.add_source(io::Error::new(io::ErrorKind::Other, "io error"));
1022        assert_eq!(err.kind(), ErrorKind::Other);
1023        assert_eq!(err.to_string(), "custom error");
1024        assert!(err.source().is_some());
1025    }
1026
1027    #[test]
1028    fn with_source() {
1029        let err = Error::with_source(
1030            ErrorKind::InvalidTypeConversion,
1031            io::Error::new(io::ErrorKind::Other, "io error"),
1032        );
1033        assert_eq!(err.kind(), ErrorKind::InvalidTypeConversion);
1034        assert_eq!(err.to_string(), "io error");
1035        assert!(err.source().is_some());
1036    }
1037}