Skip to main content

sqlx_core_oldapi/
error.rs

1//! Types for working with errors produced by SQLx.
2
3use 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
14/// A specialized `Result` type for SQLx.
15pub type Result<T> = StdResult<T, Error>;
16
17// Convenience type alias for usage within SQLx.
18// Do not make this type public.
19pub type BoxDynError = Box<dyn StdError + 'static + Send + Sync>;
20
21/// An unexpected `NULL` was encountered during decoding.
22///
23/// Returned from [`Row::get`](crate::row::Row::get) if the value from the database is `NULL`,
24/// and you are not decoding into an `Option`.
25#[derive(thiserror::Error, Debug)]
26#[error("unexpected null; try decoding as an `Option`")]
27pub struct UnexpectedNullError;
28
29/// Error indicating that a Rust type is not compatible with a SQL type.
30#[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    /// The name of the Rust type.
34    pub rust_type: String,
35    /// The SQL type name that the Rust type would map to.
36    pub rust_sql_type: String,
37    /// The actual SQL type from the database.
38    pub sql_type: String,
39    /// Optional source error that caused the mismatch.
40    #[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    /// Create a new mismatched type error without a source.
83    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    /// Create a new mismatched type error with a source error.
93    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/// Represents all the ways a method can fail within SQLx.
104#[derive(Debug, thiserror::Error)]
105#[non_exhaustive]
106pub enum Error {
107    /// Error occurred while parsing a connection string.
108    #[error("error with configuration: {0}")]
109    Configuration(#[source] BoxDynError),
110
111    /// Error returned from the database.
112    #[error("error returned from database: {0}")]
113    Database(#[source] Box<dyn DatabaseError>),
114
115    /// Error communicating with the database backend.
116    #[error("error communicating with database: {0}")]
117    Io(#[from] io::Error),
118
119    /// Error occurred while attempting to establish a TLS connection.
120    #[error("error occurred while attempting to establish a TLS connection: {0}")]
121    Tls(#[source] BoxDynError),
122
123    /// Unexpected or invalid data encountered while communicating with the database.
124    ///
125    /// This should indicate there is a programming error in a SQLx driver or there
126    /// is something corrupted with the connection to the database itself.
127    #[error("encountered unexpected or invalid data: {0}")]
128    Protocol(String),
129
130    /// No rows returned by a query that expected to return at least one row.
131    #[error("no rows returned by a query that expected to return at least one row")]
132    RowNotFound,
133
134    /// Type in query doesn't exist. Likely due to typo or missing user type.
135    #[error("type named {type_name} not found")]
136    TypeNotFound { type_name: String },
137
138    /// Column index was out of bounds.
139    #[error("column index out of bounds: the len is {len}, but the index is {index}")]
140    ColumnIndexOutOfBounds { index: usize, len: usize },
141
142    /// No column found for the given name.
143    #[error("no column found for name: {0}")]
144    ColumnNotFound(String),
145
146    /// Error occurred while decoding a value from a specific column.
147    #[error("error occurred while decoding column {index}: {source}")]
148    ColumnDecode {
149        index: String,
150
151        #[source]
152        source: BoxDynError,
153    },
154
155    /// Error occurred while decoding a value.
156    #[error("error occurred while decoding: {0}")]
157    Decode(#[source] BoxDynError),
158
159    /// A [`Pool::acquire`] timed out due to connections not becoming available or
160    /// because another task encountered too many errors while trying to open a new connection.
161    ///
162    /// [`Pool::acquire`]: crate::pool::Pool::acquire
163    #[error("pool timed out while waiting for an open connection")]
164    PoolTimedOut,
165
166    /// [`Pool::close`] was called while we were waiting in [`Pool::acquire`].
167    ///
168    /// [`Pool::acquire`]: crate::pool::Pool::acquire
169    /// [`Pool::close`]: crate::pool::Pool::close
170    #[error("attempted to acquire a connection on a closed pool")]
171    PoolClosed,
172
173    /// A background worker has crashed.
174    #[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
249/// An error that was returned from the database.
250pub trait DatabaseError: 'static + Send + Sync + StdError {
251    /// The primary, human-readable error message.
252    fn message(&self) -> &str;
253
254    /// The (SQLSTATE) code for the error.
255    fn code(&self) -> Option<Cow<'_, str>> {
256        None
257    }
258
259    /// The byte offset in the query string where the error occurred, if applicable
260    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    /// Returns the name of the constraint that triggered the error, if applicable.
279    /// If the error was caused by a conflict of a unique index, this will be the index name.
280    ///
281    /// ### Note
282    /// Currently only populated by the Postgres driver.
283    fn constraint(&self) -> Option<&str> {
284        None
285    }
286}
287
288impl dyn DatabaseError {
289    /// Downcast a reference to this generic database error to a specific
290    /// database error type.
291    ///
292    /// # Panics
293    ///
294    /// Panics if the database error type is not `E`. This is a deliberate contrast from
295    /// `Error::downcast_ref` which returns `Option<&E>`. In normal usage, you should know the
296    /// specific error type. In other cases, use `try_downcast_ref`.
297    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    /// Downcast this generic database error to a specific database error type.
307    ///
308    /// # Panics
309    ///
310    /// Panics if the database error type is not `E`. This is a deliberate contrast from
311    /// `Error::downcast` which returns `Option<E>`. In normal usage, you should know the
312    /// specific error type. In other cases, use `try_downcast`.
313    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    /// Downcast a reference to this generic database error to a specific
323    /// database error type.
324    #[inline]
325    pub fn try_downcast_ref<E: DatabaseError>(&self) -> Option<&E> {
326        self.as_error().downcast_ref()
327    }
328
329    /// Downcast this generic database error to a specific database error type.
330    #[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
366// Format an error message as a `Protocol` error
367macro_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}