1use odbc_api::{
2 handles::{slice_to_cow_utf8, Record},
3 Error as OdbcApiError,
4};
5use std::borrow::Cow;
6use std::fmt::{Display, Formatter, Result as FmtResult};
7
8pub type Result<T, E = OdbcError> = std::result::Result<T, E>;
10
11#[derive(Debug, thiserror::Error)]
13pub enum OdbcError {
14 #[error(transparent)]
16 Database(#[from] OdbcDatabaseError),
17
18 #[error("ODBC configuration error: {0}")]
20 Configuration(String),
21}
22
23impl From<OdbcApiError> for OdbcError {
24 fn from(error: OdbcApiError) -> Self {
25 Self::Database(OdbcDatabaseError::from(error))
26 }
27}
28
29impl From<OdbcError> for sqlx_core::Error {
30 fn from(error: OdbcError) -> Self {
31 match error {
32 OdbcError::Database(error) => sqlx_core::Error::Database(Box::new(error)),
33 OdbcError::Configuration(message) => sqlx_core::Error::Configuration(message.into()),
34 }
35 }
36}
37
38#[derive(Debug)]
40pub struct OdbcDatabaseError {
41 error: OdbcApiError,
42 message: String,
43 code: Option<String>,
44}
45
46impl OdbcDatabaseError {
47 fn diagnostic_record(error: &OdbcApiError) -> Option<&Record> {
48 match error {
49 OdbcApiError::Diagnostics { record, .. } => Some(record),
50 OdbcApiError::InvalidRowArraySize { record, .. } => Some(record),
51 OdbcApiError::UnsupportedOdbcApiVersion(record) => Some(record),
52 OdbcApiError::UnableToRepresentNull(record) => Some(record),
53 OdbcApiError::OracleOdbcDriverDoesNotSupport64Bit(record) => Some(record),
54 _ => None,
55 }
56 }
57
58 fn diagnostic_code(record: &Record) -> Option<String> {
59 let code = record.state.as_str();
60
61 if code.as_bytes().iter().all(|&byte| byte == 0) {
62 None
63 } else {
64 Some(code.to_owned())
65 }
66 }
67
68 pub fn message(&self) -> &str {
70 &self.message
71 }
72
73 pub fn code(&self) -> Option<Cow<'_, str>> {
75 self.code.as_deref().map(Cow::Borrowed)
76 }
77}
78
79impl From<OdbcApiError> for OdbcDatabaseError {
80 fn from(error: OdbcApiError) -> Self {
81 let record = Self::diagnostic_record(&error);
82 let message = record
83 .map(|record| slice_to_cow_utf8(&record.message).into_owned())
84 .filter(|message| !message.is_empty())
85 .unwrap_or_else(|| error.to_string());
86 let code = record.and_then(Self::diagnostic_code);
87
88 Self {
89 error,
90 message,
91 code,
92 }
93 }
94}
95
96impl Display for OdbcDatabaseError {
97 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
98 Display::fmt(&self.error, f)
99 }
100}
101
102impl std::error::Error for OdbcDatabaseError {}
103
104impl sqlx_core::error::DatabaseError for OdbcDatabaseError {
105 fn message(&self) -> &str {
106 self.message()
107 }
108
109 fn code(&self) -> Option<Cow<'_, str>> {
110 self.code()
111 }
112
113 fn as_error(&self) -> &(dyn std::error::Error + Send + Sync + 'static) {
114 self
115 }
116
117 fn as_error_mut(&mut self) -> &mut (dyn std::error::Error + Send + Sync + 'static) {
118 self
119 }
120
121 fn into_error(self: Box<Self>) -> Box<dyn std::error::Error + Send + Sync + 'static> {
122 self
123 }
124
125 fn kind(&self) -> sqlx_core::error::ErrorKind {
126 sqlx_core::error::ErrorKind::Other
127 }
128}
129
130#[cfg(test)]
131mod tests {
132 use super::*;
133 use odbc_api::handles::{Record, SqlChar, State};
134
135 fn sql_chars(text: &str) -> Vec<SqlChar> {
136 text.bytes().collect()
137 }
138
139 #[test]
140 fn database_error_uses_odbc_diagnostics_for_message_and_code() {
141 let error = OdbcDatabaseError::from(OdbcApiError::Diagnostics {
142 function: "SQLExecDirect",
143 record: Record {
144 state: State(*b"HY000"),
145 native_error: 1234,
146 message: sql_chars("syntax error near FROM"),
147 },
148 });
149
150 assert_eq!(error.message(), "syntax error near FROM");
151 assert_eq!(error.code().as_deref(), Some("HY000"));
152 }
153}