1use std::fmt;
6use thiserror::Error;
7
8#[derive(Error, Debug)]
10pub enum ExasolError {
11 #[error(transparent)]
13 Connection(#[from] ConnectionError),
14
15 #[error(transparent)]
17 Query(#[from] QueryError),
18
19 #[error(transparent)]
21 Conversion(#[from] ConversionError),
22
23 #[error(transparent)]
25 Transport(#[from] TransportError),
26}
27
28#[derive(Error, Debug)]
30pub enum ConnectionError {
31 #[error("Failed to connect to {host}:{port}: {message}")]
33 ConnectionFailed {
34 host: String,
35 port: u16,
36 message: String,
37 },
38
39 #[error("Authentication failed: {0}")]
41 AuthenticationFailed(String),
42
43 #[error("Invalid connection parameter '{parameter}': {message}")]
45 InvalidParameter { parameter: String, message: String },
46
47 #[error("Failed to parse connection string: {0}")]
49 ParseError(String),
50
51 #[error("Connection timeout after {timeout_ms}ms")]
53 Timeout { timeout_ms: u64 },
54
55 #[error("Connection is closed")]
57 ConnectionClosed,
58
59 #[error("TLS error: {0}")]
61 TlsError(String),
62}
63
64#[derive(Error, Debug)]
66pub enum QueryError {
67 #[error("SQL syntax error at position {position}: {message}")]
69 SyntaxError { position: usize, message: String },
70
71 #[error("Query execution failed: {0}")]
73 ExecutionFailed(String),
74
75 #[error("Query timeout after {timeout_ms}ms")]
77 Timeout { timeout_ms: u64 },
78
79 #[error("Invalid query state: {0}")]
81 InvalidState(String),
82
83 #[error("Parameter binding error for parameter {index}: {message}")]
85 ParameterBindingError { index: usize, message: String },
86
87 #[error("Result set not available: {0}")]
89 NoResultSet(String),
90
91 #[error("Transaction error: {0}")]
93 TransactionError(String),
94
95 #[error("Potential SQL injection detected")]
97 SqlInjectionDetected,
98
99 #[error("Prepared statement has been closed")]
101 StatementClosed,
102
103 #[error("Expected row count but received result set")]
105 UnexpectedResultSet,
106}
107
108#[derive(Error, Debug)]
110pub enum ConversionError {
111 #[error("Unsupported Exasol type: {exasol_type}")]
113 UnsupportedType { exasol_type: String },
114
115 #[error("Failed to convert value at row {row}, column {column}: {message}")]
117 ValueConversionFailed {
118 row: usize,
119 column: usize,
120 message: String,
121 },
122
123 #[error("Schema mismatch: {0}")]
125 SchemaMismatch(String),
126
127 #[error("Invalid data format: {0}")]
129 InvalidFormat(String),
130
131 #[error("Numeric overflow at row {row}, column {column}")]
133 NumericOverflow { row: usize, column: usize },
134
135 #[error("Invalid UTF-8 string at row {row}, column {column}")]
137 InvalidUtf8 { row: usize, column: usize },
138
139 #[error("Arrow error: {0}")]
141 ArrowError(String),
142}
143
144#[derive(Error, Debug)]
146pub enum TransportError {
147 #[error("WebSocket error: {0}")]
149 WebSocketError(String),
150
151 #[error("Serialization error: {0}")]
153 SerializationError(String),
154
155 #[error("Deserialization error: {0}")]
157 DeserializationError(String),
158
159 #[error("Protocol error: {0}")]
161 ProtocolError(String),
162
163 #[error("Invalid server response: {0}")]
165 InvalidResponse(String),
166
167 #[error("Network I/O error: {0}")]
169 IoError(String),
170
171 #[error("Failed to send message: {0}")]
173 SendError(String),
174
175 #[error("Failed to receive message: {0}")]
177 ReceiveError(String),
178
179 #[error("TLS error: {0}")]
181 TlsError(String),
182}
183
184#[derive(Debug, Clone, Copy, PartialEq, Eq)]
188pub enum AdbcErrorCode {
189 Unknown = 0,
191 Connection = 1,
193 Query = 2,
195 InvalidArgument = 3,
197 InvalidState = 4,
199 NotImplemented = 5,
201 Timeout = 6,
203}
204
205impl fmt::Display for AdbcErrorCode {
206 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
207 match self {
208 AdbcErrorCode::Unknown => write!(f, "UNKNOWN"),
209 AdbcErrorCode::Connection => write!(f, "CONNECTION"),
210 AdbcErrorCode::Query => write!(f, "QUERY"),
211 AdbcErrorCode::InvalidArgument => write!(f, "INVALID_ARGUMENT"),
212 AdbcErrorCode::InvalidState => write!(f, "INVALID_STATE"),
213 AdbcErrorCode::NotImplemented => write!(f, "NOT_IMPLEMENTED"),
214 AdbcErrorCode::Timeout => write!(f, "TIMEOUT"),
215 }
216 }
217}
218
219impl ExasolError {
220 pub fn to_adbc_code(&self) -> AdbcErrorCode {
222 match self {
223 ExasolError::Connection(e) => e.to_adbc_code(),
224 ExasolError::Query(e) => e.to_adbc_code(),
225 ExasolError::Conversion(_) => AdbcErrorCode::Query,
226 ExasolError::Transport(_) => AdbcErrorCode::Connection,
227 }
228 }
229}
230
231impl ConnectionError {
232 pub fn to_adbc_code(&self) -> AdbcErrorCode {
234 match self {
235 ConnectionError::Timeout { .. } => AdbcErrorCode::Timeout,
236 ConnectionError::InvalidParameter { .. } => AdbcErrorCode::InvalidArgument,
237 _ => AdbcErrorCode::Connection,
238 }
239 }
240}
241
242impl QueryError {
243 pub fn to_adbc_code(&self) -> AdbcErrorCode {
245 match self {
246 QueryError::Timeout { .. } => AdbcErrorCode::Timeout,
247 QueryError::InvalidState(_) => AdbcErrorCode::InvalidState,
248 QueryError::ParameterBindingError { .. } => AdbcErrorCode::InvalidArgument,
249 QueryError::StatementClosed => AdbcErrorCode::InvalidState,
250 _ => AdbcErrorCode::Query,
251 }
252 }
253}
254
255impl From<arrow::error::ArrowError> for ConversionError {
257 fn from(err: arrow::error::ArrowError) -> Self {
258 ConversionError::ArrowError(err.to_string())
259 }
260}
261
262impl From<serde_json::Error> for TransportError {
263 fn from(err: serde_json::Error) -> Self {
264 TransportError::SerializationError(err.to_string())
265 }
266}
267
268impl From<tokio_tungstenite::tungstenite::Error> for TransportError {
269 fn from(err: tokio_tungstenite::tungstenite::Error) -> Self {
270 TransportError::WebSocketError(err.to_string())
271 }
272}
273
274#[cfg(test)]
275mod tests {
276 use super::*;
277
278 #[test]
279 fn test_connection_error_display() {
280 let err = ConnectionError::ConnectionFailed {
281 host: "localhost".to_string(),
282 port: 8563,
283 message: "Connection refused".to_string(),
284 };
285 assert!(err.to_string().contains("localhost"));
286 assert!(err.to_string().contains("8563"));
287 }
288
289 #[test]
290 fn test_query_error_display() {
291 let err = QueryError::SyntaxError {
292 position: 10,
293 message: "Unexpected token".to_string(),
294 };
295 assert!(err.to_string().contains("position 10"));
296 }
297
298 #[test]
299 fn test_conversion_error_display() {
300 let err = ConversionError::ValueConversionFailed {
301 row: 5,
302 column: 2,
303 message: "Invalid number format".to_string(),
304 };
305 assert!(err.to_string().contains("row 5"));
306 assert!(err.to_string().contains("column 2"));
307 }
308
309 #[test]
310 fn test_adbc_error_code_mapping() {
311 let err = ExasolError::Connection(ConnectionError::Timeout { timeout_ms: 5000 });
312 assert_eq!(err.to_adbc_code(), AdbcErrorCode::Timeout);
313
314 let err = ExasolError::Query(QueryError::InvalidState("Bad state".to_string()));
315 assert_eq!(err.to_adbc_code(), AdbcErrorCode::InvalidState);
316 }
317
318 #[test]
319 fn test_error_code_display() {
320 assert_eq!(AdbcErrorCode::Connection.to_string(), "CONNECTION");
321 assert_eq!(AdbcErrorCode::Timeout.to_string(), "TIMEOUT");
322 }
323
324 #[test]
325 fn test_transport_tls_error() {
326 let err = TransportError::TlsError("Certificate validation failed".to_string());
327 assert!(err.to_string().contains("TLS error"));
328 assert!(err.to_string().contains("Certificate validation failed"));
329 }
330
331 #[test]
332 fn test_statement_closed_error() {
333 let err = QueryError::StatementClosed;
334 assert!(err.to_string().contains("closed"));
335 assert_eq!(err.to_adbc_code(), AdbcErrorCode::InvalidState);
336 }
337
338 #[test]
339 fn test_unexpected_result_set_error() {
340 let err = QueryError::UnexpectedResultSet;
341 assert!(err.to_string().contains("result set"));
342 }
343}