1use std::io;
2
3use thiserror::Error as ThisError;
4
5use crate::handles::{Diagnostics, Record as DiagnosticRecord, SqlResult, log_diagnostics};
6
7#[derive(Debug)]
9pub struct TooLargeBufferSize {
10 pub num_elements: usize,
12 pub element_size: usize,
14}
15
16impl TooLargeBufferSize {
17 pub fn add_context(self, buffer_index: u16) -> Error {
20 Error::TooLargeColumnBufferSize {
21 buffer_index,
22 num_elements: self.num_elements,
23 element_size: self.element_size,
24 }
25 }
26}
27
28#[cfg(feature = "odbc_version_3_5")]
29const ODBC_VERSION_STRING: &str = "3.5";
30#[cfg(not(feature = "odbc_version_3_5"))]
31const ODBC_VERSION_STRING: &str = "3.80";
32
33#[derive(Debug, ThisError)]
34pub enum Error {
36 #[error("Failed to set connection pooling.")]
39 FailedSettingConnectionPooling,
40 #[error("Failed to allocate ODBC Environment.")]
44 FailedAllocatingEnvironment,
45 #[error(
48 "No Diagnostics available. The ODBC function call to {} returned an error. Sadly neither \
49 the ODBC driver manager, nor the driver were polite enough to leave a diagnostic record \
50 specifying what exactly went wrong.",
51 function
52 )]
53 NoDiagnostics {
54 function: &'static str,
56 },
57 #[error("ODBC emitted an error calling '{function}':\n{record}")]
60 Diagnostics {
61 record: DiagnosticRecord,
63 function: &'static str,
65 },
66 #[error("The dialog shown to provide or complete the connection string has been aborted.")]
68 AbortedConnectionStringCompletion,
69 #[error(
71 "The ODBC diver manager installed in your system does not seem to support ODBC API version \
72 {ODBC_VERSION_STRING}. Which is required by this application. Most likely you need to \
73 update your driver manager. Your driver manager is most likely unixODBC if you run on a \
74 Linux. Diagnostic record returned by SQLSetEnvAttr:\n{0}"
75 )]
76 UnsupportedOdbcApiVersion(DiagnosticRecord),
77 #[error("Sending data to the database at statement execution time failed. IO error:\n{0}")]
79 FailedReadingInput(io::Error),
80 #[error(
85 "An invalid row array size (aka. batch size) has been set. The ODBC drivers should just \
86 emit a warning and emmit smaller batches, but not all do (yours does not at least). Try \
87 fetching data from the database in smaller batches.\nRow array size (aka. batch size): \
88 {size}\n Diagnostic record returned by SQLSetEnvAttr:\n{record}"
89 )]
90 InvalidRowArraySize {
91 record: DiagnosticRecord,
92 size: usize,
93 },
94 #[error(
95 "Tried to retrieve a value from the database. The value turned out to be `NULL` yet this \
96 turned out to not be representable. So the application is written as if the value could \
97 never be `NULL` in the datasource, yet the in actuallity a `NULL` has been returned. \
98 Diagnostic record returned:\n{0}"
99 )]
100 UnableToRepresentNull(DiagnosticRecord),
101 #[error(
105 "SQLFetch came back with an error indicating you specified an invalid SQL Type. You very \
106 likely did not do that however. Actually SQLFetch is not supposed to return that error \
107 type. You should have received it back than you were still binding columns or parameters. \
108 All this is circumstancial evidence that you are using an Oracle Database and want to use \
109 64Bit integers, which are not supported by Oracles ODBC driver manager. In case this \
110 diagnose is wrong the original error is:\n{0}."
111 )]
112 OracleOdbcDriverDoesNotSupport64Bit(DiagnosticRecord),
113 #[error(
114 "There is not enough memory to allocate enough memory for a column buffer. Number of \
115 elements requested for the column buffer: {num_elements}; Size needed to hold the largest \
116 possible element: {element_size}."
117 )]
118 TooLargeColumnBufferSize {
119 buffer_index: u16,
122 num_elements: usize,
123 element_size: usize,
125 },
126 #[error(
127 "A value (at least one) is too large to be written into the allocated buffer without \
128 truncation. Size in bytes indicated by ODBC driver: {indicator:?}"
129 )]
130 TooLargeValueForBuffer {
131 indicator: Option<usize>,
134 buffer_index: usize,
136 },
137}
138
139impl Error {
140 fn provide_context_for_diagnostic<F>(self, f: F) -> Self
143 where
144 F: FnOnce(DiagnosticRecord, &'static str) -> Error,
145 {
146 if let Error::Diagnostics { record, function } = self {
147 f(record, function)
148 } else {
149 self
150 }
151 }
152}
153
154pub(crate) trait ExtendResult {
156 fn provide_context_for_diagnostic<F>(self, f: F) -> Self
157 where
158 F: FnOnce(DiagnosticRecord, &'static str) -> Error;
159}
160
161impl<T> ExtendResult for Result<T, Error> {
162 fn provide_context_for_diagnostic<F>(self, f: F) -> Self
163 where
164 F: FnOnce(DiagnosticRecord, &'static str) -> Error,
165 {
166 self.map_err(|error| error.provide_context_for_diagnostic(f))
167 }
168}
169
170impl SqlResult<()> {
171 pub fn into_result_bool(self, handle: &impl Diagnostics) -> Result<bool, Error> {
175 self.on_success(|| true)
176 .on_no_data(|| false)
177 .into_result(handle)
178 }
179}
180
181impl<T> SqlResult<T> {
184 pub fn has_diganostics(&self) -> bool {
187 matches!(
188 self,
189 SqlResult::SuccessWithInfo(_) | SqlResult::Error { function: _ }
190 )
191 }
192
193 pub fn into_result(self, handle: &impl Diagnostics) -> Result<T, Error> {
198 if self.has_diganostics() {
199 log_diagnostics(handle);
200 }
201 self.into_result_without_logging(handle)
202 }
203
204 pub fn into_result_without_logging(self, handle: &impl Diagnostics) -> Result<T, Error> {
211 match self {
212 SqlResult::Success(value) | SqlResult::SuccessWithInfo(value) => Ok(value),
214 SqlResult::Error { function } => {
215 let mut record = DiagnosticRecord::with_capacity(512);
216 if record.fill_from(handle, 1) {
217 Err(Error::Diagnostics { record, function })
218 } else {
219 Err(Error::NoDiagnostics { function })
224 }
225 }
226 SqlResult::NoData => {
227 panic!(
228 "Unexepcted SQL_NO_DATA returned by ODBC function. Use `SqlResult::on_no_data` \
229 to handle it."
230 )
231 }
232 SqlResult::NeedData => {
233 panic!(
234 "Unexpected SQL_NEED_DATA returned by ODBC function. Use \
235 `SqlResult::on_need_data` to handle it."
236 )
237 }
238 SqlResult::StillExecuting => panic!(
239 "SqlResult must not be converted to result while the function is still executing."
240 ),
241 }
242 }
243
244 pub fn or_no_data(self) -> SqlResult<Option<T>> {
247 self.map(Some).on_no_data(|| None)
248 }
249}