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