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
44impl MismatchedTypeError {
45    /// Create a new mismatched type error without a source.
46    pub fn new<DB: Database, T: Type<DB>>(ty: &DB::TypeInfo) -> Self {
47        Self {
48            rust_type: type_name::<T>().to_string(),
49            rust_sql_type: T::type_info().name().to_string(),
50            sql_type: ty.name().to_string(),
51            source: None,
52        }
53    }
54
55    /// Create a new mismatched type error with a source error.
56    pub fn with_source<DB: Database, T: Type<DB>>(ty: &DB::TypeInfo, source: BoxDynError) -> Self {
57        Self {
58            rust_type: type_name::<T>().to_string(),
59            rust_sql_type: T::type_info().name().to_string(),
60            sql_type: ty.name().to_string(),
61            source: Some(source),
62        }
63    }
64}
65
66/// Represents all the ways a method can fail within SQLx.
67#[derive(Debug, thiserror::Error)]
68#[non_exhaustive]
69pub enum Error {
70    /// Error occurred while parsing a connection string.
71    #[error("error with configuration: {0}")]
72    Configuration(#[source] BoxDynError),
73
74    /// Error returned from the database.
75    #[error("error returned from database: {0}")]
76    Database(#[source] Box<dyn DatabaseError>),
77
78    /// Error communicating with the database backend.
79    #[error("error communicating with database: {0}")]
80    Io(#[from] io::Error),
81
82    /// Error occurred while attempting to establish a TLS connection.
83    #[error("error occurred while attempting to establish a TLS connection: {0}")]
84    Tls(#[source] BoxDynError),
85
86    /// Unexpected or invalid data encountered while communicating with the database.
87    ///
88    /// This should indicate there is a programming error in a SQLx driver or there
89    /// is something corrupted with the connection to the database itself.
90    #[error("encountered unexpected or invalid data: {0}")]
91    Protocol(String),
92
93    /// No rows returned by a query that expected to return at least one row.
94    #[error("no rows returned by a query that expected to return at least one row")]
95    RowNotFound,
96
97    /// Type in query doesn't exist. Likely due to typo or missing user type.
98    #[error("type named {type_name} not found")]
99    TypeNotFound { type_name: String },
100
101    /// Column index was out of bounds.
102    #[error("column index out of bounds: the len is {len}, but the index is {index}")]
103    ColumnIndexOutOfBounds { index: usize, len: usize },
104
105    /// No column found for the given name.
106    #[error("no column found for name: {0}")]
107    ColumnNotFound(String),
108
109    /// Error occurred while decoding a value from a specific column.
110    #[error("error occurred while decoding column {index}: {source}")]
111    ColumnDecode {
112        index: String,
113
114        #[source]
115        source: BoxDynError,
116    },
117
118    /// Error occurred while decoding a value.
119    #[error("error occurred while decoding: {0}")]
120    Decode(#[source] BoxDynError),
121
122    /// A [`Pool::acquire`] timed out due to connections not becoming available or
123    /// because another task encountered too many errors while trying to open a new connection.
124    ///
125    /// [`Pool::acquire`]: crate::pool::Pool::acquire
126    #[error("pool timed out while waiting for an open connection")]
127    PoolTimedOut,
128
129    /// [`Pool::close`] was called while we were waiting in [`Pool::acquire`].
130    ///
131    /// [`Pool::acquire`]: crate::pool::Pool::acquire
132    /// [`Pool::close`]: crate::pool::Pool::close
133    #[error("attempted to acquire a connection on a closed pool")]
134    PoolClosed,
135
136    /// A background worker has crashed.
137    #[error("attempted to communicate with a crashed background worker")]
138    WorkerCrashed,
139
140    #[cfg(feature = "migrate")]
141    #[error("{0}")]
142    Migrate(#[source] Box<crate::migrate::MigrateError>),
143
144    #[error("integer overflow while converting to target type")]
145    IntegerOverflow(#[source] std::num::TryFromIntError),
146}
147
148impl StdError for Box<dyn DatabaseError> {}
149
150impl Error {
151    pub fn into_database_error(self) -> Option<Box<dyn DatabaseError + 'static>> {
152        match self {
153            Error::Database(err) => Some(err),
154            _ => None,
155        }
156    }
157
158    pub fn as_database_error(&self) -> Option<&(dyn DatabaseError + 'static)> {
159        match self {
160            Error::Database(err) => Some(&**err),
161            _ => None,
162        }
163    }
164
165    #[allow(dead_code)]
166    #[inline]
167    pub(crate) fn protocol(err: impl Display) -> Self {
168        Error::Protocol(err.to_string())
169    }
170
171    #[allow(dead_code)]
172    #[inline]
173    pub(crate) fn config(err: impl StdError + Send + Sync + 'static) -> Self {
174        Error::Configuration(err.into())
175    }
176
177    #[allow(dead_code)]
178    #[inline]
179    pub(crate) fn tls<T: Into<BoxDynError>>(err: T) -> Self {
180        Error::Tls(err.into())
181    }
182}
183
184pub(crate) fn mismatched_types<DB: Database, T: Type<DB>>(ty: &DB::TypeInfo) -> BoxDynError {
185    Box::new(MismatchedTypeError {
186        rust_type: format!(
187            "{} ({}compatible with SQL type `{}`)",
188            type_name::<T>(),
189            if T::compatible(ty) { "" } else { "in" },
190            T::type_info().name()
191        ),
192        rust_sql_type: T::type_info().name().to_string(),
193        sql_type: ty.name().to_string(),
194        source: None,
195    })
196}
197
198/// An error that was returned from the database.
199pub trait DatabaseError: 'static + Send + Sync + StdError {
200    /// The primary, human-readable error message.
201    fn message(&self) -> &str;
202
203    /// The (SQLSTATE) code for the error.
204    fn code(&self) -> Option<Cow<'_, str>> {
205        None
206    }
207
208    /// The byte offset in the query string where the error occurred, if applicable
209    fn offset(&self) -> Option<usize> {
210        None
211    }
212
213    #[doc(hidden)]
214    fn as_error(&self) -> &(dyn StdError + Send + Sync + 'static);
215
216    #[doc(hidden)]
217    fn as_error_mut(&mut self) -> &mut (dyn StdError + Send + Sync + 'static);
218
219    #[doc(hidden)]
220    fn into_error(self: Box<Self>) -> Box<dyn StdError + Send + Sync + 'static>;
221
222    #[doc(hidden)]
223    fn is_transient_in_connect_phase(&self) -> bool {
224        false
225    }
226
227    /// Returns the name of the constraint that triggered the error, if applicable.
228    /// If the error was caused by a conflict of a unique index, this will be the index name.
229    ///
230    /// ### Note
231    /// Currently only populated by the Postgres driver.
232    fn constraint(&self) -> Option<&str> {
233        None
234    }
235}
236
237impl dyn DatabaseError {
238    /// Downcast a reference to this generic database error to a specific
239    /// database error type.
240    ///
241    /// # Panics
242    ///
243    /// Panics if the database error type is not `E`. This is a deliberate contrast from
244    /// `Error::downcast_ref` which returns `Option<&E>`. In normal usage, you should know the
245    /// specific error type. In other cases, use `try_downcast_ref`.
246    pub fn downcast_ref<E: DatabaseError>(&self) -> &E {
247        self.try_downcast_ref().unwrap_or_else(|| {
248            panic!(
249                "downcast to wrong DatabaseError type; original error: {}",
250                self
251            )
252        })
253    }
254
255    /// Downcast this generic database error to a specific database error type.
256    ///
257    /// # Panics
258    ///
259    /// Panics if the database error type is not `E`. This is a deliberate contrast from
260    /// `Error::downcast` which returns `Option<E>`. In normal usage, you should know the
261    /// specific error type. In other cases, use `try_downcast`.
262    pub fn downcast<E: DatabaseError>(self: Box<Self>) -> Box<E> {
263        self.try_downcast().unwrap_or_else(|e| {
264            panic!(
265                "downcast to wrong DatabaseError type; original error: {}",
266                e
267            )
268        })
269    }
270
271    /// Downcast a reference to this generic database error to a specific
272    /// database error type.
273    #[inline]
274    pub fn try_downcast_ref<E: DatabaseError>(&self) -> Option<&E> {
275        self.as_error().downcast_ref()
276    }
277
278    /// Downcast this generic database error to a specific database error type.
279    #[inline]
280    pub fn try_downcast<E: DatabaseError>(self: Box<Self>) -> StdResult<Box<E>, Box<Self>> {
281        if self.as_error().is::<E>() {
282            Ok(self.into_error().downcast().unwrap())
283        } else {
284            Err(self)
285        }
286    }
287}
288
289impl<E> From<E> for Error
290where
291    E: DatabaseError,
292{
293    #[inline]
294    fn from(error: E) -> Self {
295        Error::Database(Box::new(error))
296    }
297}
298
299#[cfg(feature = "migrate")]
300impl From<crate::migrate::MigrateError> for Error {
301    #[inline]
302    fn from(error: crate::migrate::MigrateError) -> Self {
303        Error::Migrate(Box::new(error))
304    }
305}
306
307#[cfg(feature = "_tls-native-tls")]
308impl From<sqlx_rt::native_tls::Error> for Error {
309    #[inline]
310    fn from(error: sqlx_rt::native_tls::Error) -> Self {
311        Error::Tls(Box::new(error))
312    }
313}
314
315// Format an error message as a `Protocol` error
316macro_rules! err_protocol {
317    ($expr:expr) => {
318        $crate::error::Error::Protocol($expr.into())
319    };
320
321    ($fmt:expr, $($arg:tt)*) => {
322        $crate::error::Error::Protocol(format!($fmt, $($arg)*))
323    };
324}