sqlx_core_oldapi/postgres/
error.rs

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