Skip to main content

sqlx_core_oldapi/odbc/
error.rs

1use crate::error::DatabaseError;
2use odbc_api::{
3    handles::{slice_to_cow_utf8, Record},
4    Error as OdbcApiError,
5};
6use std::borrow::Cow;
7use std::fmt::{Display, Formatter, Result as FmtResult};
8
9#[derive(Debug)]
10pub struct OdbcDatabaseError {
11    error: OdbcApiError,
12    message: String,
13    code: Option<String>,
14}
15
16impl OdbcDatabaseError {
17    fn diagnostic_record(error: &OdbcApiError) -> Option<&Record> {
18        match error {
19            OdbcApiError::Diagnostics { record, .. } => Some(record),
20            OdbcApiError::InvalidRowArraySize { record, .. } => Some(record),
21            OdbcApiError::UnsupportedOdbcApiVersion(record) => Some(record),
22            OdbcApiError::UnableToRepresentNull(record) => Some(record),
23            OdbcApiError::OracleOdbcDriverDoesNotSupport64Bit(record) => Some(record),
24            _ => None,
25        }
26    }
27
28    fn diagnostic_code(record: &Record) -> Option<String> {
29        let code = record.state.as_str();
30
31        if code.as_bytes().iter().all(|&byte| byte == 0) {
32            None
33        } else {
34            Some(code.to_owned())
35        }
36    }
37}
38
39impl From<OdbcApiError> for OdbcDatabaseError {
40    fn from(error: OdbcApiError) -> Self {
41        let record = Self::diagnostic_record(&error);
42        let message = record
43            .map(|record| slice_to_cow_utf8(&record.message).into_owned())
44            .filter(|message| !message.is_empty())
45            .unwrap_or_else(|| error.to_string());
46        let code = record.and_then(Self::diagnostic_code);
47
48        Self {
49            error,
50            message,
51            code,
52        }
53    }
54}
55
56impl Display for OdbcDatabaseError {
57    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
58        Display::fmt(&self.error, f)
59    }
60}
61
62impl std::error::Error for OdbcDatabaseError {}
63
64impl DatabaseError for OdbcDatabaseError {
65    fn message(&self) -> &str {
66        &self.message
67    }
68    fn code(&self) -> Option<Cow<'_, str>> {
69        self.code.as_deref().map(Cow::Borrowed)
70    }
71    fn as_error(&self) -> &(dyn std::error::Error + Send + Sync + 'static) {
72        self
73    }
74    fn as_error_mut(&mut self) -> &mut (dyn std::error::Error + Send + Sync + 'static) {
75        self
76    }
77    fn into_error(self: Box<Self>) -> Box<dyn std::error::Error + Send + Sync + 'static> {
78        self
79    }
80}
81
82impl From<OdbcApiError> for crate::error::Error {
83    fn from(value: OdbcApiError) -> Self {
84        crate::error::Error::Database(Box::new(OdbcDatabaseError::from(value)))
85    }
86}
87
88#[cfg(test)]
89mod tests {
90    use super::*;
91    use crate::error::DatabaseError;
92    use odbc_api::handles::{Record, SqlChar, State};
93
94    fn sql_chars(text: &str) -> Vec<SqlChar> {
95        text.bytes().map(Into::into).collect()
96    }
97
98    #[test]
99    fn database_error_uses_odbc_diagnostics_for_message_and_code() {
100        let error = OdbcDatabaseError::from(OdbcApiError::Diagnostics {
101            function: "SQLExecDirect",
102            record: Record {
103                state: State(*b"HY000"),
104                native_error: 1234,
105                message: sql_chars("syntax error near FROM"),
106            },
107        });
108
109        assert_eq!(error.message(), "syntax error near FROM");
110        assert_eq!(error.code().as_deref(), Some("HY000"));
111    }
112}