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/// 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 occurred while decoding a value.
82    #[error("error occurred while decoding: {0}")]
83    Decode(#[source] BoxDynError),
84
85    /// A [`Pool::acquire`] timed out due to connections not becoming available or
86    /// because another task encountered too many errors while trying to open a new connection.
87    ///
88    /// [`Pool::acquire`]: crate::pool::Pool::acquire
89    #[error("pool timed out while waiting for an open connection")]
90    PoolTimedOut,
91
92    /// [`Pool::close`] was called while we were waiting in [`Pool::acquire`].
93    ///
94    /// [`Pool::acquire`]: crate::pool::Pool::acquire
95    /// [`Pool::close`]: crate::pool::Pool::close
96    #[error("attempted to acquire a connection on a closed pool")]
97    PoolClosed,
98
99    /// A background worker has crashed.
100    #[error("attempted to communicate with a crashed background worker")]
101    WorkerCrashed,
102
103    #[cfg(feature = "migrate")]
104    #[error("{0}")]
105    Migrate(#[source] Box<crate::migrate::MigrateError>),
106
107    #[error("integer overflow while converting to target type")]
108    IntegerOverflow(#[source] std::num::TryFromIntError),
109}
110
111impl StdError for Box<dyn DatabaseError> {}
112
113impl Error {
114    pub fn into_database_error(self) -> Option<Box<dyn DatabaseError + 'static>> {
115        match self {
116            Error::Database(err) => Some(err),
117            _ => None,
118        }
119    }
120
121    pub fn as_database_error(&self) -> Option<&(dyn DatabaseError + 'static)> {
122        match self {
123            Error::Database(err) => Some(&**err),
124            _ => None,
125        }
126    }
127
128    #[allow(dead_code)]
129    #[inline]
130    pub(crate) fn protocol(err: impl Display) -> Self {
131        Error::Protocol(err.to_string())
132    }
133
134    #[allow(dead_code)]
135    #[inline]
136    pub(crate) fn config(err: impl StdError + Send + Sync + 'static) -> Self {
137        Error::Configuration(err.into())
138    }
139
140    #[allow(dead_code)]
141    #[inline]
142    pub(crate) fn tls<T: Into<BoxDynError>>(err: T) -> Self {
143        Error::Tls(err.into())
144    }
145}
146
147pub(crate) fn mismatched_types<DB: Database, T: Type<DB>>(ty: &DB::TypeInfo) -> BoxDynError {
148    // TODO: `#name` only produces `TINYINT` but perhaps we want to show `TINYINT(1)`
149    format!(
150        "mismatched types; Rust type `{}` (as SQL type `{}`) is not compatible with SQL type `{}`",
151        type_name::<T>(),
152        T::type_info().name(),
153        ty.name()
154    )
155    .into()
156}
157
158/// An error that was returned from the database.
159pub trait DatabaseError: 'static + Send + Sync + StdError {
160    /// The primary, human-readable error message.
161    fn message(&self) -> &str;
162
163    /// The (SQLSTATE) code for the error.
164    fn code(&self) -> Option<Cow<'_, str>> {
165        None
166    }
167
168    /// The byte offset in the query string where the error occurred, if applicable
169    fn offset(&self) -> Option<usize> {
170        None
171    }
172
173    #[doc(hidden)]
174    fn as_error(&self) -> &(dyn StdError + Send + Sync + 'static);
175
176    #[doc(hidden)]
177    fn as_error_mut(&mut self) -> &mut (dyn StdError + Send + Sync + 'static);
178
179    #[doc(hidden)]
180    fn into_error(self: Box<Self>) -> Box<dyn StdError + Send + Sync + 'static>;
181
182    #[doc(hidden)]
183    fn is_transient_in_connect_phase(&self) -> bool {
184        false
185    }
186
187    /// Returns the name of the constraint that triggered the error, if applicable.
188    /// If the error was caused by a conflict of a unique index, this will be the index name.
189    ///
190    /// ### Note
191    /// Currently only populated by the Postgres driver.
192    fn constraint(&self) -> Option<&str> {
193        None
194    }
195}
196
197impl dyn DatabaseError {
198    /// Downcast a reference to this generic database error to a specific
199    /// database error type.
200    ///
201    /// # Panics
202    ///
203    /// Panics if the database error type is not `E`. This is a deliberate contrast from
204    /// `Error::downcast_ref` which returns `Option<&E>`. In normal usage, you should know the
205    /// specific error type. In other cases, use `try_downcast_ref`.
206    pub fn downcast_ref<E: DatabaseError>(&self) -> &E {
207        self.try_downcast_ref().unwrap_or_else(|| {
208            panic!(
209                "downcast to wrong DatabaseError type; original error: {}",
210                self
211            )
212        })
213    }
214
215    /// Downcast this generic database error to a specific database error type.
216    ///
217    /// # Panics
218    ///
219    /// Panics if the database error type is not `E`. This is a deliberate contrast from
220    /// `Error::downcast` which returns `Option<E>`. In normal usage, you should know the
221    /// specific error type. In other cases, use `try_downcast`.
222    pub fn downcast<E: DatabaseError>(self: Box<Self>) -> Box<E> {
223        self.try_downcast().unwrap_or_else(|e| {
224            panic!(
225                "downcast to wrong DatabaseError type; original error: {}",
226                e
227            )
228        })
229    }
230
231    /// Downcast a reference to this generic database error to a specific
232    /// database error type.
233    #[inline]
234    pub fn try_downcast_ref<E: DatabaseError>(&self) -> Option<&E> {
235        self.as_error().downcast_ref()
236    }
237
238    /// Downcast this generic database error to a specific database error type.
239    #[inline]
240    pub fn try_downcast<E: DatabaseError>(self: Box<Self>) -> StdResult<Box<E>, Box<Self>> {
241        if self.as_error().is::<E>() {
242            Ok(self.into_error().downcast().unwrap())
243        } else {
244            Err(self)
245        }
246    }
247}
248
249impl<E> From<E> for Error
250where
251    E: DatabaseError,
252{
253    #[inline]
254    fn from(error: E) -> Self {
255        Error::Database(Box::new(error))
256    }
257}
258
259#[cfg(feature = "migrate")]
260impl From<crate::migrate::MigrateError> for Error {
261    #[inline]
262    fn from(error: crate::migrate::MigrateError) -> Self {
263        Error::Migrate(Box::new(error))
264    }
265}
266
267#[cfg(feature = "_tls-native-tls")]
268impl From<sqlx_rt::native_tls::Error> for Error {
269    #[inline]
270    fn from(error: sqlx_rt::native_tls::Error) -> Self {
271        Error::Tls(Box::new(error))
272    }
273}
274
275// Format an error message as a `Protocol` error
276macro_rules! err_protocol {
277    ($expr:expr) => {
278        $crate::error::Error::Protocol($expr.into())
279    };
280
281    ($fmt:expr, $($arg:tt)*) => {
282        $crate::error::Error::Protocol(format!($fmt, $($arg)*))
283    };
284}