sqlx_core_oldapi/
error.rs1use std::any::type_name;
4use std::borrow::Cow;
5use std::error::Error as StdError;
6use std::fmt::Display;
7use std::io;
8use std::result::Result as StdResult;
9
10use crate::database::Database;
11use crate::type_info::TypeInfo;
12use crate::types::Type;
13
14pub type Result<T> = StdResult<T, Error>;
16
17pub type BoxDynError = Box<dyn StdError + 'static + Send + Sync>;
20
21#[derive(thiserror::Error, Debug)]
26#[error("unexpected null; try decoding as an `Option`")]
27pub struct UnexpectedNullError;
28
29#[derive(thiserror::Error, Debug)]
31#[error("mismatched types; Rust type `{rust_type}` (as SQL type `{rust_sql_type}`) could not be decoded into SQL type `{sql_type}`")]
32pub struct MismatchedTypeError {
33 pub rust_type: String,
35 pub rust_sql_type: String,
37 pub sql_type: String,
39 #[source]
41 pub source: Option<BoxDynError>,
42}
43
44fn rust_sql_type<DB: Database, T: Type<DB>>() -> String {
45 if is_any_db::<DB>() {
46 return "<unknown>".to_string();
47 }
48
49 T::type_info().name().to_string()
50}
51
52#[cfg(all(
53 feature = "any",
54 any(
55 feature = "postgres",
56 feature = "mysql",
57 feature = "mssql",
58 feature = "sqlite",
59 feature = "odbc"
60 )
61))]
62fn is_any_db<DB: Database>() -> bool {
63 std::any::TypeId::of::<DB>() == std::any::TypeId::of::<crate::any::Any>()
64}
65
66#[cfg(not(all(
67 feature = "any",
68 any(
69 feature = "postgres",
70 feature = "mysql",
71 feature = "mssql",
72 feature = "sqlite",
73 feature = "odbc"
74 )
75)))]
76fn is_any_db<DB: Database>() -> bool {
77 let _ = std::any::TypeId::of::<DB>();
78 false
79}
80
81impl MismatchedTypeError {
82 pub fn new<DB: Database, T: Type<DB>>(ty: &DB::TypeInfo) -> Self {
84 Self {
85 rust_type: type_name::<T>().to_string(),
86 rust_sql_type: rust_sql_type::<DB, T>(),
87 sql_type: ty.name().to_string(),
88 source: None,
89 }
90 }
91
92 pub fn with_source<DB: Database, T: Type<DB>>(ty: &DB::TypeInfo, source: BoxDynError) -> Self {
94 Self {
95 rust_type: type_name::<T>().to_string(),
96 rust_sql_type: rust_sql_type::<DB, T>(),
97 sql_type: ty.name().to_string(),
98 source: Some(source),
99 }
100 }
101}
102
103#[derive(Debug, thiserror::Error)]
105#[non_exhaustive]
106pub enum Error {
107 #[error("error with configuration: {0}")]
109 Configuration(#[source] BoxDynError),
110
111 #[error("error returned from database: {0}")]
113 Database(#[source] Box<dyn DatabaseError>),
114
115 #[error("error communicating with database: {0}")]
117 Io(#[from] io::Error),
118
119 #[error("error occurred while attempting to establish a TLS connection: {0}")]
121 Tls(#[source] BoxDynError),
122
123 #[error("encountered unexpected or invalid data: {0}")]
128 Protocol(String),
129
130 #[error("no rows returned by a query that expected to return at least one row")]
132 RowNotFound,
133
134 #[error("type named {type_name} not found")]
136 TypeNotFound { type_name: String },
137
138 #[error("column index out of bounds: the len is {len}, but the index is {index}")]
140 ColumnIndexOutOfBounds { index: usize, len: usize },
141
142 #[error("no column found for name: {0}")]
144 ColumnNotFound(String),
145
146 #[error("error occurred while decoding column {index}: {source}")]
148 ColumnDecode {
149 index: String,
150
151 #[source]
152 source: BoxDynError,
153 },
154
155 #[error("error occurred while decoding: {0}")]
157 Decode(#[source] BoxDynError),
158
159 #[error("pool timed out while waiting for an open connection")]
164 PoolTimedOut,
165
166 #[error("attempted to acquire a connection on a closed pool")]
171 PoolClosed,
172
173 #[error("attempted to communicate with a crashed background worker")]
175 WorkerCrashed,
176
177 #[cfg(feature = "migrate")]
178 #[error("{0}")]
179 Migrate(#[source] Box<crate::migrate::MigrateError>),
180
181 #[error("integer overflow while converting to target type")]
182 IntegerOverflow(#[source] std::num::TryFromIntError),
183}
184
185impl StdError for Box<dyn DatabaseError> {}
186
187impl Error {
188 pub fn into_database_error(self) -> Option<Box<dyn DatabaseError + 'static>> {
189 match self {
190 Error::Database(err) => Some(err),
191 _ => None,
192 }
193 }
194
195 pub fn as_database_error(&self) -> Option<&(dyn DatabaseError + 'static)> {
196 match self {
197 Error::Database(err) => Some(&**err),
198 _ => None,
199 }
200 }
201
202 #[allow(dead_code)]
203 #[inline]
204 pub(crate) fn protocol(err: impl Display) -> Self {
205 Error::Protocol(err.to_string())
206 }
207
208 #[allow(dead_code)]
209 #[inline]
210 pub(crate) fn config(err: impl StdError + Send + Sync + 'static) -> Self {
211 Error::Configuration(err.into())
212 }
213
214 #[allow(dead_code)]
215 #[inline]
216 pub(crate) fn tls<T: Into<BoxDynError>>(err: T) -> Self {
217 Error::Tls(err.into())
218 }
219}
220
221pub(crate) fn mismatched_types<DB: Database, T: Type<DB>>(ty: &DB::TypeInfo) -> BoxDynError {
222 let rust_sql_type = rust_sql_type::<DB, T>();
223 Box::new(MismatchedTypeError {
224 rust_type: format!(
225 "{} ({}compatible with SQL type `{}`)",
226 type_name::<T>(),
227 if T::compatible(ty) { "" } else { "in" },
228 rust_sql_type
229 ),
230 rust_sql_type,
231 sql_type: ty.name().to_string(),
232 source: None,
233 })
234}
235
236#[cfg(all(test, feature = "any", feature = "postgres"))]
237mod tests {
238 use crate::any::Any;
239 use crate::error::mismatched_types;
240 use crate::postgres::PgTypeInfo;
241
242 #[test]
243 fn mismatched_types_any_does_not_panic() {
244 let ty = crate::any::AnyTypeInfo::from(PgTypeInfo::with_name("TEXT"));
245 assert!(std::panic::catch_unwind(|| mismatched_types::<Any, i32>(&ty)).is_ok());
246 }
247}
248
249pub trait DatabaseError: 'static + Send + Sync + StdError {
251 fn message(&self) -> &str;
253
254 fn code(&self) -> Option<Cow<'_, str>> {
256 None
257 }
258
259 fn offset(&self) -> Option<usize> {
261 None
262 }
263
264 #[doc(hidden)]
265 fn as_error(&self) -> &(dyn StdError + Send + Sync + 'static);
266
267 #[doc(hidden)]
268 fn as_error_mut(&mut self) -> &mut (dyn StdError + Send + Sync + 'static);
269
270 #[doc(hidden)]
271 fn into_error(self: Box<Self>) -> Box<dyn StdError + Send + Sync + 'static>;
272
273 #[doc(hidden)]
274 fn is_transient_in_connect_phase(&self) -> bool {
275 false
276 }
277
278 fn constraint(&self) -> Option<&str> {
284 None
285 }
286}
287
288impl dyn DatabaseError {
289 pub fn downcast_ref<E: DatabaseError>(&self) -> &E {
298 self.try_downcast_ref().unwrap_or_else(|| {
299 panic!(
300 "downcast to wrong DatabaseError type; original error: {}",
301 self
302 )
303 })
304 }
305
306 pub fn downcast<E: DatabaseError>(self: Box<Self>) -> Box<E> {
314 self.try_downcast().unwrap_or_else(|e| {
315 panic!(
316 "downcast to wrong DatabaseError type; original error: {}",
317 e
318 )
319 })
320 }
321
322 #[inline]
325 pub fn try_downcast_ref<E: DatabaseError>(&self) -> Option<&E> {
326 self.as_error().downcast_ref()
327 }
328
329 #[inline]
331 pub fn try_downcast<E: DatabaseError>(self: Box<Self>) -> StdResult<Box<E>, Box<Self>> {
332 if self.as_error().is::<E>() {
333 Ok(self.into_error().downcast().unwrap())
334 } else {
335 Err(self)
336 }
337 }
338}
339
340impl<E> From<E> for Error
341where
342 E: DatabaseError,
343{
344 #[inline]
345 fn from(error: E) -> Self {
346 Error::Database(Box::new(error))
347 }
348}
349
350#[cfg(feature = "migrate")]
351impl From<crate::migrate::MigrateError> for Error {
352 #[inline]
353 fn from(error: crate::migrate::MigrateError) -> Self {
354 Error::Migrate(Box::new(error))
355 }
356}
357
358#[cfg(feature = "_tls-native-tls")]
359impl From<sqlx_rt::native_tls::Error> for Error {
360 #[inline]
361 fn from(error: sqlx_rt::native_tls::Error) -> Self {
362 Error::Tls(Box::new(error))
363 }
364}
365
366macro_rules! err_protocol {
368 ($expr:expr) => {
369 $crate::error::Error::Protocol($expr.into())
370 };
371
372 ($fmt:expr, $($arg:tt)*) => {
373 $crate::error::Error::Protocol(format!($fmt, $($arg)*))
374 };
375}