Skip to main content

odbc/
diagnostics.rs

1use super::{ffi, safe};
2use std::{fmt, cmp};
3use std::ffi::CStr;
4use std::error::Error;
5
6pub const MAX_DIAGNOSTIC_MESSAGE_SIZE: usize = 1024;
7
8/// ODBC Diagnostic Record
9///
10/// The `description` method of the `std::error::Error` trait only returns the message. Use
11/// `std::fmt::Display` to retrieve status code and other information.
12pub struct DiagnosticRecord {
13    // All elements but the last one, may not be null. The last one must be null.
14    state: [ffi::SQLCHAR; ffi::SQL_SQLSTATE_SIZE + 1],
15    // Must at least contain one null
16    message: [ffi::SQLCHAR; MAX_DIAGNOSTIC_MESSAGE_SIZE],
17    // The numbers of characters in message not null
18    message_length: ffi::SQLSMALLINT,
19    native_error: ffi::SQLINTEGER,
20    message_string: String,
21}
22
23impl DiagnosticRecord {
24    /// get raw state string data.
25    pub fn get_raw_state(&self) -> &[u8] {
26        &self.state
27    }
28    /// get raw diagnostics message for avoiding encoding error.
29    pub fn get_raw_message(&self) -> &[u8] {
30        &self.message[0..self.message_length as usize]
31    }
32    /// get native odbc error number
33    pub fn get_native_error(&self) -> i32 {
34        self.native_error
35    }
36    /// constructs an empty diagnostics message.
37    /// this is needed for errors where the driver doesn't return any diagnostics info.
38    pub fn empty() -> DiagnosticRecord {
39        let message = b"No SQL-driver error information available.";
40        let mut rec = DiagnosticRecord {
41            state: b"HY000\0".clone(),
42            message: [0u8; MAX_DIAGNOSTIC_MESSAGE_SIZE],
43            native_error: -1,
44            message_length: message.len() as ffi::SQLSMALLINT,
45            message_string: String::from(""),
46        };
47        rec.message[..message.len()].copy_from_slice(message);
48        rec
49    }
50}
51
52impl fmt::Display for DiagnosticRecord {
53    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
54        // Todo: replace unwrap with `?` in Rust 1.17
55        let state = CStr::from_bytes_with_nul(&self.state).unwrap();
56
57        write!(
58            f,
59            "State: {}, Native error: {}, Message: {}",
60            state.to_str().unwrap(),
61            self.native_error,
62            self.message_string,
63        )
64    }
65}
66
67impl fmt::Debug for DiagnosticRecord {
68    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
69        fmt::Display::fmt(self, f)
70    }
71}
72
73impl Error for DiagnosticRecord {
74    fn description(&self) -> &str {
75        &self.message_string
76    }
77    fn cause(&self) -> Option<& dyn Error> {
78        None
79    }
80}
81
82/// Allows retrieving a diagnostic record, describing errors (or lack thereof) during the last
83/// operation.
84pub trait GetDiagRec {
85    /// Retrieves a diagnostic record
86    ///
87    /// `record_number` - Record numbers start at one. If you pass an number < 1 the function will
88    /// panic. If no record is available for the number specified none is returned.
89    fn get_diag_rec(&self, record_number: i16) -> Option<DiagnosticRecord>;
90}
91
92impl<D> GetDiagRec for D
93where
94    D: safe::Diagnostics,
95{
96    fn get_diag_rec(&self, record_number: i16) -> Option<(DiagnosticRecord)> {
97        use safe::ReturnOption::*;
98        let mut message = [0; MAX_DIAGNOSTIC_MESSAGE_SIZE];
99        match self.diagnostics(record_number, &mut message) {
100            Success(result) | Info(result) => {
101                // The message could be larger than the supplied buffer, so we need to limit the message length to the buffer size.
102                let mut message_length = cmp::min(result.text_length, MAX_DIAGNOSTIC_MESSAGE_SIZE as ffi::SQLSMALLINT - 1);
103                // Some drivers pad the message with null-chars (which is still a valid C string, but not a valid Rust string).
104                while message_length > 0 && message[(message_length - 1) as usize] == 0 {
105                    message_length = message_length - 1;
106                }
107                Some(DiagnosticRecord {
108                    state: result.state,
109                    native_error: result.native_error,
110                    message_length,
111                    message,
112                    message_string: unsafe {
113                        ::environment::OS_ENCODING.decode(&message[0..message_length as usize]).0.into_owned()
114                    },
115                })
116            }
117            NoData(()) => None,
118            Error(()) => panic!("Diagnostics returned error for record number {}. Record numbers have to be at least 1.", record_number),
119        }
120    }
121}
122
123#[cfg(test)]
124mod test {
125
126    use super::*;
127
128    impl DiagnosticRecord {
129        fn new() -> DiagnosticRecord {
130            DiagnosticRecord {
131                state: [0u8; ffi::SQL_SQLSTATE_SIZE + 1],
132                message: [0u8; MAX_DIAGNOSTIC_MESSAGE_SIZE],
133                native_error: 0,
134                message_length: 0,
135                message_string: String::from(""),
136            }
137        }
138    }
139
140    #[test]
141    fn formatting() {
142
143        // build diagnostic record
144        let message = b"[Microsoft][ODBC Driver Manager] Function sequence error\0";
145        let mut rec = DiagnosticRecord::new();
146        rec.state = b"HY010\0".clone();
147        rec.message_string = CStr::from_bytes_with_nul(message).unwrap().to_str().unwrap().to_owned();
148        rec.message_length = 56;
149        for i in 0..(rec.message_length as usize) {
150            rec.message[i] = message[i];
151        }
152
153        // test formatting
154        assert_eq!(
155            format!("{}", rec),
156            "State: HY010, Native error: 0, Message: [Microsoft][ODBC Driver Manager] \
157             Function sequence error"
158        );
159    }
160}