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
128impl StdError for Box<dyn DatabaseError> {}
129
130impl Error {
131    pub fn into_database_error(self) -> Option<Box<dyn DatabaseError + 'static>> {
132        match self {
133            Error::Database(err) => Some(err),
134            _ => None,
135        }
136    }
137
138    pub fn as_database_error(&self) -> Option<&(dyn DatabaseError + 'static)> {
139        match self {
140            Error::Database(err) => Some(&**err),
141            _ => None,
142        }
143    }
144
145    #[doc(hidden)]
146    #[inline]
147    pub fn protocol(err: impl Display) -> Self {
148        Error::Protocol(err.to_string())
149    }
150
151    #[doc(hidden)]
152    #[inline]
153    pub fn database(err: impl DatabaseError) -> Self {
154        Error::Database(Box::new(err))
155    }
156
157    #[doc(hidden)]
158    #[inline]
159    pub fn config(err: impl StdError + Send + Sync + 'static) -> Self {
160        Error::Configuration(err.into())
161    }
162
163    pub(crate) fn tls(err: impl Into<Box<dyn StdError + Send + Sync + 'static>>) -> Self {
164        Error::Tls(err.into())
165    }
166
167    #[doc(hidden)]
168    #[inline]
169    pub fn decode(err: impl Into<Box<dyn StdError + Send + Sync + 'static>>) -> Self {
170        Error::Decode(err.into())
171    }
172}
173
174pub fn mismatched_types<DB: Database, T: Type<DB>>(ty: &DB::TypeInfo) -> BoxDynError {
175    // TODO: `#name` only produces `TINYINT` but perhaps we want to show `TINYINT(1)`
176    format!(
177        "mismatched types; Rust type `{}` (as SQL type `{}`) is not compatible with SQL type `{}`",
178        type_name::<T>(),
179        T::type_info().name(),
180        ty.name()
181    )
182    .into()
183}
184
185/// The error kind.
186///
187/// This enum is to be used to identify frequent errors that can be handled by the program.
188/// Although it currently only supports constraint violations, the type may grow in the future.
189#[derive(Debug, PartialEq, Eq)]
190#[non_exhaustive]
191pub enum ErrorKind {
192    /// Unique/primary key constraint violation.
193    UniqueViolation,
194    /// Foreign key constraint violation.
195    ForeignKeyViolation,
196    /// Not-null constraint violation.
197    NotNullViolation,
198    /// Check constraint violation.
199    CheckViolation,
200    /// An unmapped error.
201    Other,
202}
203
204/// An error that was returned from the database.
205pub trait DatabaseError: 'static + Send + Sync + StdError {
206    /// The primary, human-readable error message.
207    fn message(&self) -> &str;
208
209    /// The (SQLSTATE) code for the error.
210    fn code(&self) -> Option<Cow<'_, str>> {
211        None
212    }
213
214    #[doc(hidden)]
215    fn as_error(&self) -> &(dyn StdError + Send + Sync + 'static);
216
217    #[doc(hidden)]
218    fn as_error_mut(&mut self) -> &mut (dyn StdError + Send + Sync + 'static);
219
220    #[doc(hidden)]
221    fn into_error(self: Box<Self>) -> Box<dyn StdError + Send + Sync + 'static>;
222
223    #[doc(hidden)]
224    fn is_transient_in_connect_phase(&self) -> bool {
225        false
226    }
227
228    /// Returns the name of the constraint that triggered the error, if applicable.
229    /// If the error was caused by a conflict of a unique index, this will be the index name.
230    ///
231    /// ### Note
232    /// Currently only populated by the Postgres driver.
233    fn constraint(&self) -> Option<&str> {
234        None
235    }
236
237    /// Returns the name of the table that was affected by the error, if applicable.
238    ///
239    /// ### Note
240    /// Currently only populated by the Postgres driver.
241    fn table(&self) -> Option<&str> {
242        None
243    }
244
245    /// Returns the kind of the error, if supported.
246    ///
247    /// ### Note
248    /// Not all back-ends behave the same when reporting the error code.
249    fn kind(&self) -> ErrorKind;
250
251    /// Returns whether the error kind is a violation of a unique/primary key constraint.
252    fn is_unique_violation(&self) -> bool {
253        matches!(self.kind(), ErrorKind::UniqueViolation)
254    }
255
256    /// Returns whether the error kind is a violation of a foreign key.
257    fn is_foreign_key_violation(&self) -> bool {
258        matches!(self.kind(), ErrorKind::ForeignKeyViolation)
259    }
260
261    /// Returns whether the error kind is a violation of a check.
262    fn is_check_violation(&self) -> bool {
263        matches!(self.kind(), ErrorKind::CheckViolation)
264    }
265}
266
267impl dyn DatabaseError {
268    /// Downcast a reference to this generic database error to a specific
269    /// database error type.
270    ///
271    /// # Panics
272    ///
273    /// Panics if the database error type is not `E`. This is a deliberate contrast from
274    /// `Error::downcast_ref` which returns `Option<&E>`. In normal usage, you should know the
275    /// specific error type. In other cases, use `try_downcast_ref`.
276    pub fn downcast_ref<E: DatabaseError>(&self) -> &E {
277        self.try_downcast_ref().unwrap_or_else(|| {
278            panic!("downcast to wrong DatabaseError type; original error: {self}")
279        })
280    }
281
282    /// Downcast this generic database error to a specific database error type.
283    ///
284    /// # Panics
285    ///
286    /// Panics if the database error type is not `E`. This is a deliberate contrast from
287    /// `Error::downcast` which returns `Option<E>`. In normal usage, you should know the
288    /// specific error type. In other cases, use `try_downcast`.
289    pub fn downcast<E: DatabaseError>(self: Box<Self>) -> Box<E> {
290        self.try_downcast()
291            .unwrap_or_else(|e| panic!("downcast to wrong DatabaseError type; original error: {e}"))
292    }
293
294    /// Downcast a reference to this generic database error to a specific
295    /// database error type.
296    #[inline]
297    pub fn try_downcast_ref<E: DatabaseError>(&self) -> Option<&E> {
298        self.as_error().downcast_ref()
299    }
300
301    /// Downcast this generic database error to a specific database error type.
302    #[inline]
303    pub fn try_downcast<E: DatabaseError>(self: Box<Self>) -> Result<Box<E>, Box<Self>> {
304        if self.as_error().is::<E>() {
305            Ok(self.into_error().downcast().unwrap())
306        } else {
307            Err(self)
308        }
309    }
310}
311
312impl<E> From<E> for Error
313where
314    E: DatabaseError,
315{
316    #[inline]
317    fn from(error: E) -> Self {
318        Error::Database(Box::new(error))
319    }
320}
321
322#[cfg(feature = "migrate")]
323impl From<crate::migrate::MigrateError> for Error {
324    #[inline]
325    fn from(error: crate::migrate::MigrateError) -> Self {
326        Error::Migrate(Box::new(error))
327    }
328}
329
330/// Format an error message as a `Protocol` error
331#[macro_export]
332macro_rules! err_protocol {
333    ($($fmt_args:tt)*) => {
334        $crate::error::Error::Protocol(
335            format!(
336                "{} ({}:{})",
337                // Note: the format string needs to be unmodified (e.g. by `concat!()`)
338                // for implicit formatting arguments to work
339                format_args!($($fmt_args)*),
340                module_path!(),
341                line!(),
342            )
343        )
344    };
345}