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