1use std::error::Error as StdError;
2use std::fmt::{self, Debug, Display, Formatter};
34use atoi::atoi;
5use smallvec::alloc::borrow::Cow;
67pub(crate) use sqlx_core::error::*;
89use crate::message::{Notice, PgSeverity};
1011/// An error returned from the PostgreSQL database.
12pub struct PgDatabaseError(pub(crate) Notice);
1314// Error message fields are documented:
15// https://www.postgresql.org/docs/current/protocol-error-fields.html
1617impl PgDatabaseError {
18#[inline]
19pub fn severity(&self) -> PgSeverity {
20self.0.severity()
21 }
2223/// The [SQLSTATE](https://www.postgresql.org/docs/current/errcodes-appendix.html) code for
24 /// this error.
25#[inline]
26pub fn code(&self) -> &str {
27self.0.code()
28 }
2930/// The primary human-readable error message. This should be accurate but
31 /// terse (typically one line).
32#[inline]
33pub fn message(&self) -> &str {
34self.0.message()
35 }
3637/// An optional secondary error message carrying more detail about the problem.
38 /// Might run to multiple lines.
39#[inline]
40pub fn detail(&self) -> Option<&str> {
41self.0.get(b'D')
42 }
4344/// 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]
48pub fn hint(&self) -> Option<&str> {
49self.0.get(b'H')
50 }
5152/// Indicates an error cursor position as an index into the original query string; or,
53 /// a position into an internally generated query.
54#[inline]
55pub fn position(&self) -> Option<PgErrorPosition<'_>> {
56self.0
57.get_raw(b'P')
58 .and_then(atoi)
59 .map(PgErrorPosition::Original)
60 .or_else(|| {
61let position = self.0.get_raw(b'p').and_then(atoi)?;
62let query = self.0.get(b'q')?;
6364Some(PgErrorPosition::Internal { position, query })
65 })
66 }
6768/// 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.
71pub fn r#where(&self) -> Option<&str> {
72self.0.get(b'W')
73 }
7475/// If this error is with a specific database object, the
76 /// name of the schema containing that object, if any.
77pub fn schema(&self) -> Option<&str> {
78self.0.get(b's')
79 }
8081/// If this error is with a specific table, the name of the table.
82pub fn table(&self) -> Option<&str> {
83self.0.get(b't')
84 }
8586/// If the error is with a specific table column, the name of the column.
87pub fn column(&self) -> Option<&str> {
88self.0.get(b'c')
89 }
9091/// If the error is with a specific data type, the name of the data type.
92pub fn data_type(&self) -> Option<&str> {
93self.0.get(b'd')
94 }
9596/// 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.
99pub fn constraint(&self) -> Option<&str> {
100self.0.get(b'n')
101 }
102103/// The file name of the source-code location where this error was reported.
104pub fn file(&self) -> Option<&str> {
105self.0.get(b'F')
106 }
107108/// The line number of the source-code location where this error was reported.
109pub fn line(&self) -> Option<usize> {
110self.0.get_raw(b'L').and_then(atoi)
111 }
112113/// The name of the source-code routine reporting this error.
114pub fn routine(&self) -> Option<&str> {
115self.0.get(b'R')
116 }
117}
118119#[derive(Debug, Eq, PartialEq)]
120pub enum PgErrorPosition<'a> {
121/// A position (in characters) into the original query.
122Original(usize),
123124/// A position into the internally-generated query.
125Internal {
126/// The position in characters.
127position: usize,
128129/// The text of a failed internally-generated command. This could be, for example,
130 /// the SQL query issued by a PL/pgSQL function.
131query: &'a str,
132 },
133}
134135impl Debug for PgDatabaseError {
136fn 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}
156157impl Display for PgDatabaseError {
158fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
159 f.write_str(self.message())
160 }
161}
162163impl StdError for PgDatabaseError {}
164165impl DatabaseError for PgDatabaseError {
166fn message(&self) -> &str {
167self.message()
168 }
169170fn code(&self) -> Option<Cow<'_, str>> {
171Some(Cow::Borrowed(self.code()))
172 }
173174#[doc(hidden)]
175fn as_error(&self) -> &(dyn StdError + Send + Sync + 'static) {
176self
177}
178179#[doc(hidden)]
180fn as_error_mut(&mut self) -> &mut (dyn StdError + Send + Sync + 'static) {
181self
182}
183184#[doc(hidden)]
185fn into_error(self: Box<Self>) -> BoxDynError {
186self
187}
188189fn is_transient_in_connect_phase(&self) -> bool {
190// https://www.postgresql.org/docs/current/errcodes-appendix.html
191[
192// too_many_connections
193 // This may be returned if we just un-gracefully closed a connection,
194 // give the database a chance to notice it and clean it up.
195"53300",
196// cannot_connect_now
197 // Returned if the database is still starting up.
198"57P03",
199 ]
200 .contains(&self.code())
201 }
202203fn constraint(&self) -> Option<&str> {
204self.constraint()
205 }
206207fn table(&self) -> Option<&str> {
208self.table()
209 }
210211fn kind(&self) -> ErrorKind {
212match self.code() {
213 error_codes::UNIQUE_VIOLATION => ErrorKind::UniqueViolation,
214 error_codes::FOREIGN_KEY_VIOLATION => ErrorKind::ForeignKeyViolation,
215 error_codes::NOT_NULL_VIOLATION => ErrorKind::NotNullViolation,
216 error_codes::CHECK_VIOLATION => ErrorKind::CheckViolation,
217_ => ErrorKind::Other,
218 }
219 }
220}
221222/// For reference: <https://www.postgresql.org/docs/current/errcodes-appendix.html>
223pub(crate) mod error_codes {
224/// Caused when a unique or primary key is violated.
225pub const UNIQUE_VIOLATION: &str = "23505";
226/// Caused when a foreign key is violated.
227pub const FOREIGN_KEY_VIOLATION: &str = "23503";
228/// Caused when a column marked as NOT NULL received a null value.
229pub const NOT_NULL_VIOLATION: &str = "23502";
230/// Caused when a check constraint is violated.
231pub const CHECK_VIOLATION: &str = "23514";
232}