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