Skip to main content

sqlx_postgres/
error.rs

1use std::error::Error as StdError;
2use std::fmt::{self, Debug, Display, Formatter};
3
4use atoi::atoi;
5use smallvec::alloc::borrow::Cow;
6use sqlx_core::bytes::Bytes;
7pub(crate) use sqlx_core::error::*;
8
9use crate::message::{BackendMessage, BackendMessageFormat, Notice, PgSeverity};
10
11/// An error returned from the PostgreSQL database.
12pub struct PgDatabaseError(pub(crate) Notice);
13
14// Error message fields are documented:
15// https://www.postgresql.org/docs/current/protocol-error-fields.html
16
17impl PgDatabaseError {
18    #[inline]
19    pub fn severity(&self) -> PgSeverity {
20        self.0.severity()
21    }
22
23    /// The [SQLSTATE](https://www.postgresql.org/docs/current/errcodes-appendix.html) code for
24    /// this error.
25    #[inline]
26    pub fn code(&self) -> &str {
27        self.0.code()
28    }
29
30    /// The primary human-readable error message. This should be accurate but
31    /// terse (typically one line).
32    #[inline]
33    pub fn message(&self) -> &str {
34        self.0.message()
35    }
36
37    /// An optional secondary error message carrying more detail about the problem.
38    /// Might run to multiple lines.
39    #[inline]
40    pub fn detail(&self) -> Option<&str> {
41        self.0.get(b'D')
42    }
43
44    /// An optional suggestion what to do about the problem. This is intended to differ from
45    /// `detail` in that it offers advice (potentially inappropriate) rather than hard facts.
46    /// Might run to multiple lines.
47    #[inline]
48    pub fn hint(&self) -> Option<&str> {
49        self.0.get(b'H')
50    }
51
52    /// Indicates an error cursor position as an index into the original query string; or,
53    /// a position into an internally generated query.
54    #[inline]
55    pub fn position(&self) -> Option<PgErrorPosition<'_>> {
56        self.0
57            .get_raw(b'P')
58            .and_then(atoi)
59            .map(PgErrorPosition::Original)
60            .or_else(|| {
61                let position = self.0.get_raw(b'p').and_then(atoi)?;
62                let query = self.0.get(b'q')?;
63
64                Some(PgErrorPosition::Internal { position, query })
65            })
66    }
67
68    /// An indication of the context in which the error occurred. Presently this includes a call
69    /// stack traceback of active procedural language functions and internally-generated queries.
70    /// The trace is one entry per line, most recent first.
71    pub fn r#where(&self) -> Option<&str> {
72        self.0.get(b'W')
73    }
74
75    /// If this error is with a specific database object, the
76    /// name of the schema containing that object, if any.
77    pub fn schema(&self) -> Option<&str> {
78        self.0.get(b's')
79    }
80
81    /// If this error is with a specific table, the name of the table.
82    pub fn table(&self) -> Option<&str> {
83        self.0.get(b't')
84    }
85
86    /// If the error is with a specific table column, the name of the column.
87    pub fn column(&self) -> Option<&str> {
88        self.0.get(b'c')
89    }
90
91    /// If the error is with a specific data type, the name of the data type.
92    pub fn data_type(&self) -> Option<&str> {
93        self.0.get(b'd')
94    }
95
96    /// If the error is with a specific constraint, the name of the constraint.
97    /// For this purpose, indexes are constraints, even if they weren't created
98    /// with constraint syntax.
99    pub fn constraint(&self) -> Option<&str> {
100        self.0.get(b'n')
101    }
102
103    /// The file name of the source-code location where this error was reported.
104    pub fn file(&self) -> Option<&str> {
105        self.0.get(b'F')
106    }
107
108    /// The line number of the source-code location where this error was reported.
109    pub fn line(&self) -> Option<usize> {
110        self.0.get_raw(b'L').and_then(atoi)
111    }
112
113    /// The name of the source-code routine reporting this error.
114    pub fn routine(&self) -> Option<&str> {
115        self.0.get(b'R')
116    }
117}
118
119#[derive(Debug, Eq, PartialEq)]
120pub enum PgErrorPosition<'a> {
121    /// A position (in characters) into the original query.
122    Original(usize),
123
124    /// A position into the internally-generated query.
125    Internal {
126        /// The position in characters.
127        position: usize,
128
129        /// The text of a failed internally-generated command. This could be, for example,
130        /// the SQL query issued by a PL/pgSQL function.
131        query: &'a str,
132    },
133}
134
135impl Debug for PgDatabaseError {
136    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
137        f.debug_struct("PgDatabaseError")
138            .field("severity", &self.severity())
139            .field("code", &self.code())
140            .field("message", &self.message())
141            .field("detail", &self.detail())
142            .field("hint", &self.hint())
143            .field("position", &self.position())
144            .field("where", &self.r#where())
145            .field("schema", &self.schema())
146            .field("table", &self.table())
147            .field("column", &self.column())
148            .field("data_type", &self.data_type())
149            .field("constraint", &self.constraint())
150            .field("file", &self.file())
151            .field("line", &self.line())
152            .field("routine", &self.routine())
153            .finish()
154    }
155}
156
157impl Display for PgDatabaseError {
158    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
159        f.write_str(self.message())?;
160        if let Some(line) = self.line() {
161            write!(f, " at line {line}")?;
162        }
163        Ok(())
164    }
165}
166
167impl StdError for PgDatabaseError {}
168
169impl DatabaseError for PgDatabaseError {
170    fn message(&self) -> &str {
171        self.message()
172    }
173
174    fn code(&self) -> Option<Cow<'_, str>> {
175        Some(Cow::Borrowed(self.code()))
176    }
177
178    #[doc(hidden)]
179    fn as_error(&self) -> &(dyn StdError + Send + Sync + 'static) {
180        self
181    }
182
183    #[doc(hidden)]
184    fn as_error_mut(&mut self) -> &mut (dyn StdError + Send + Sync + 'static) {
185        self
186    }
187
188    #[doc(hidden)]
189    fn into_error(self: Box<Self>) -> BoxDynError {
190        self
191    }
192
193    fn is_transient_in_connect_phase(&self) -> bool {
194        // https://www.postgresql.org/docs/current/errcodes-appendix.html
195        [
196            // too_many_connections
197            // This may be returned if we just un-gracefully closed a connection,
198            // give the database a chance to notice it and clean it up.
199            "53300",
200            // cannot_connect_now
201            // Returned if the database is still starting up.
202            "57P03",
203        ]
204        .contains(&self.code())
205    }
206
207    fn constraint(&self) -> Option<&str> {
208        self.constraint()
209    }
210
211    fn table(&self) -> Option<&str> {
212        self.table()
213    }
214
215    fn kind(&self) -> ErrorKind {
216        match self.code() {
217            error_codes::UNIQUE_VIOLATION => ErrorKind::UniqueViolation,
218            error_codes::FOREIGN_KEY_VIOLATION => ErrorKind::ForeignKeyViolation,
219            error_codes::NOT_NULL_VIOLATION => ErrorKind::NotNullViolation,
220            error_codes::CHECK_VIOLATION => ErrorKind::CheckViolation,
221            error_codes::EXCLUSION_VIOLATION => ErrorKind::ExclusionViolation,
222            _ => ErrorKind::Other,
223        }
224    }
225}
226
227// ErrorResponse is the same structure as NoticeResponse but a different format code.
228impl BackendMessage for PgDatabaseError {
229    const FORMAT: BackendMessageFormat = BackendMessageFormat::ErrorResponse;
230
231    #[inline(always)]
232    fn decode_body(buf: Bytes) -> std::result::Result<Self, Error> {
233        Ok(Self(Notice::decode_body(buf)?))
234    }
235}
236
237/// For reference: <https://www.postgresql.org/docs/current/errcodes-appendix.html>
238pub(crate) mod error_codes {
239    /// Caused when a unique or primary key is violated.
240    pub const UNIQUE_VIOLATION: &str = "23505";
241    /// Caused when a foreign key is violated.
242    pub const FOREIGN_KEY_VIOLATION: &str = "23503";
243    /// Caused when a column marked as NOT NULL received a null value.
244    pub const NOT_NULL_VIOLATION: &str = "23502";
245    /// Caused when a check constraint is violated.
246    pub const CHECK_VIOLATION: &str = "23514";
247    /// Caused when a exclude constraint is violated.
248    pub const EXCLUSION_VIOLATION: &str = "23P01";
249}