1use crate::protocol::backend::{ErrorResponseBody, NoticeResponseBody};
8use fallible_iterator::FallibleIterator;
9
10#[derive(Debug, Clone, Default)]
39#[non_exhaustive]
40pub struct PgServerError {
41 pub severity: String,
43 pub severity_v: Option<String>,
45 pub code: String,
47 pub message: String,
49 pub detail: Option<String>,
51 pub hint: Option<String>,
53 pub position: Option<u32>,
55 pub internal_position: Option<u32>,
57 pub internal_query: Option<String>,
59 pub where_: Option<String>,
61 pub schema: Option<String>,
63 pub table: Option<String>,
65 pub column: Option<String>,
67 pub data_type: Option<String>,
69 pub constraint: Option<String>,
71 pub file: Option<String>,
73 pub line: Option<u32>,
75 pub routine: Option<String>,
77}
78
79impl PgServerError {
80 pub fn from_fields(fields: Vec<(u8, String)>) -> Self {
83 let mut err = PgServerError::default();
84 for (code, value) in fields {
85 match code {
86 b'S' => err.severity = value,
87 b'V' => err.severity_v = Some(value),
88 b'C' => err.code = value,
89 b'M' => err.message = value,
90 b'D' => err.detail = Some(value),
91 b'H' => err.hint = Some(value),
92 b'P' => err.position = value.parse().ok(),
93 b'p' => err.internal_position = value.parse().ok(),
94 b'q' => err.internal_query = Some(value),
95 b'W' => err.where_ = Some(value),
96 b's' => err.schema = Some(value),
97 b't' => err.table = Some(value),
98 b'c' => err.column = Some(value),
99 b'd' => err.data_type = Some(value),
100 b'n' => err.constraint = Some(value),
101 b'F' => err.file = Some(value),
102 b'L' => err.line = value.parse().ok(),
103 b'R' => err.routine = Some(value),
104 _ => {} }
106 }
107 err
108 }
109
110 pub fn from_error_body(body: &ErrorResponseBody) -> Result<Self, std::io::Error> {
112 let mut fields = Vec::new();
113 let mut iter = body.fields();
114 while let Some(field) = iter.next()? {
115 let value = std::str::from_utf8(field.value_bytes())
116 .unwrap_or("")
117 .to_string();
118 fields.push((field.type_(), value));
119 }
120 Ok(Self::from_fields(fields))
121 }
122
123 pub fn from_notice_body(body: &NoticeResponseBody) -> Result<Self, std::io::Error> {
125 let mut fields = Vec::new();
126 let mut iter = body.fields();
127 while let Some(field) = iter.next()? {
128 let value = std::str::from_utf8(field.value_bytes())
129 .unwrap_or("")
130 .to_string();
131 fields.push((field.type_(), value));
132 }
133 Ok(Self::from_fields(fields))
134 }
135
136 pub fn code(&self) -> &str {
140 &self.code
141 }
142
143 pub fn is_class(&self, class: &str) -> bool {
153 self.code.starts_with(class)
154 }
155
156 pub fn is_integrity_constraint_violation(&self) -> bool {
158 self.is_class("23")
159 }
160
161 pub fn is_unique_violation(&self) -> bool {
163 self.code == "23505"
164 }
165
166 pub fn is_foreign_key_violation(&self) -> bool {
168 self.code == "23503"
169 }
170
171 pub fn is_not_null_violation(&self) -> bool {
173 self.code == "23502"
174 }
175
176 pub fn is_check_violation(&self) -> bool {
178 self.code == "23514"
179 }
180
181 pub fn is_exclusion_violation(&self) -> bool {
183 self.code == "23P01"
184 }
185
186 pub fn is_syntax_error(&self) -> bool {
188 self.is_class("42")
189 }
190
191 pub fn is_insufficient_privilege(&self) -> bool {
193 self.code == "42501"
194 }
195
196 pub fn is_undefined_table(&self) -> bool {
198 self.code == "42P01"
199 }
200
201 pub fn is_undefined_column(&self) -> bool {
203 self.code == "42703"
204 }
205
206 pub fn is_serialization_failure(&self) -> bool {
208 self.code == "40001"
209 }
210
211 pub fn is_deadlock_detected(&self) -> bool {
213 self.code == "40P01"
214 }
215
216 pub fn is_connection_exception(&self) -> bool {
218 self.is_class("08")
219 }
220
221 pub fn is_connection_does_not_exist(&self) -> bool {
223 self.code == "08003"
224 }
225
226 pub fn is_connection_failure(&self) -> bool {
228 self.code == "08006"
229 }
230
231 pub fn is_sqlclient_unable_to_establish_sqlconnection(&self) -> bool {
233 self.code == "08001"
234 }
235
236 pub fn is_query_canceled(&self) -> bool {
238 self.code == "57014"
239 }
240
241 pub fn is_admin_shutdown(&self) -> bool {
243 self.code == "57P01"
244 }
245
246 pub fn is_crash_shutdown(&self) -> bool {
248 self.code == "57P02"
249 }
250
251 pub fn is_cannot_connect_now(&self) -> bool {
253 self.code == "57P03"
254 }
255
256 pub fn is_database_dropped(&self) -> bool {
258 self.code == "57P04"
259 }
260
261 pub fn is_idle_session_timeout(&self) -> bool {
263 self.code == "57P05"
264 }
265
266 pub fn is_fatal(&self) -> bool {
268 self.severity == "FATAL" || self.severity == "PANIC"
269 }
270
271 pub fn is_warning_or_less(&self) -> bool {
274 matches!(
275 self.severity.as_str(),
276 "WARNING" | "NOTICE" | "DEBUG" | "INFO" | "LOG"
277 )
278 }
279
280 pub fn schema(&self) -> Option<&str> {
286 self.schema.as_deref()
287 }
288
289 pub fn table(&self) -> Option<&str> {
291 self.table.as_deref()
292 }
293
294 pub fn column(&self) -> Option<&str> {
296 self.column.as_deref()
297 }
298
299 pub fn constraint(&self) -> Option<&str> {
301 self.constraint.as_deref()
302 }
303
304 pub fn detail(&self) -> Option<&str> {
306 self.detail.as_deref()
307 }
308
309 pub fn hint(&self) -> Option<&str> {
311 self.hint.as_deref()
312 }
313
314 pub fn position(&self) -> Option<u32> {
316 self.position
317 }
318}
319
320impl std::fmt::Display for PgServerError {
321 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
322 write!(
323 f,
324 "{}: {} (SQLSTATE {})",
325 self.severity, self.message, self.code
326 )?;
327 if let Some(detail) = &self.detail {
328 write!(f, "\nDETAIL: {}", detail)?;
329 }
330 if let Some(hint) = &self.hint {
331 write!(f, "\nHINT: {}", hint)?;
332 }
333 if let Some(position) = self.position {
334 write!(f, "\nPOSITION: {}", position)?;
335 }
336 Ok(())
337 }
338}
339
340impl std::error::Error for PgServerError {}
341
342#[cfg(test)]
347mod tests {
348 #![allow(clippy::field_reassign_with_default)]
349
350 use super::*;
351
352 #[test]
353 fn test_from_fields_all_fields() {
354 let fields = vec![
355 (b'S', "ERROR".to_string()),
356 (b'V', "ERROR".to_string()),
357 (b'C', "23505".to_string()),
358 (
359 b'M',
360 "duplicate key value violates unique constraint".to_string(),
361 ),
362 (b'D', "Key (id)=(1) already exists.".to_string()),
363 (b'H', "Try a different value.".to_string()),
364 (b'P', "42".to_string()),
365 (b'p', "10".to_string()),
366 (b'q', "SELECT ...".to_string()),
367 (b'W', "PL/pgSQL function ...".to_string()),
368 (b's', "public".to_string()),
369 (b't', "users".to_string()),
370 (b'c', "id".to_string()),
371 (b'd', "integer".to_string()),
372 (b'n', "users_pkey".to_string()),
373 (b'F', "nbtinsert.c".to_string()),
374 (b'L', "532".to_string()),
375 (b'R', "_bt_check_unique".to_string()),
376 ];
377
378 let err = PgServerError::from_fields(fields);
379 assert_eq!(err.severity, "ERROR");
380 assert_eq!(err.severity_v.as_deref(), Some("ERROR"));
381 assert_eq!(err.code, "23505");
382 assert_eq!(
383 err.message,
384 "duplicate key value violates unique constraint"
385 );
386 assert_eq!(err.detail.as_deref(), Some("Key (id)=(1) already exists."));
387 assert_eq!(err.hint.as_deref(), Some("Try a different value."));
388 assert_eq!(err.position, Some(42));
389 assert_eq!(err.internal_position, Some(10));
390 assert_eq!(err.internal_query.as_deref(), Some("SELECT ..."));
391 assert_eq!(err.where_.as_deref(), Some("PL/pgSQL function ..."));
392 assert_eq!(err.schema.as_deref(), Some("public"));
393 assert_eq!(err.table.as_deref(), Some("users"));
394 assert_eq!(err.column.as_deref(), Some("id"));
395 assert_eq!(err.data_type.as_deref(), Some("integer"));
396 assert_eq!(err.constraint.as_deref(), Some("users_pkey"));
397 assert_eq!(err.file.as_deref(), Some("nbtinsert.c"));
398 assert_eq!(err.line, Some(532));
399 assert_eq!(err.routine.as_deref(), Some("_bt_check_unique"));
400 }
401
402 #[test]
403 fn test_from_fields_minimal() {
404 let fields = vec![
405 (b'S', "ERROR".to_string()),
406 (b'C', "42601".to_string()),
407 (b'M', "syntax error".to_string()),
408 ];
409
410 let err = PgServerError::from_fields(fields);
411 assert_eq!(err.severity, "ERROR");
412 assert_eq!(err.code, "42601");
413 assert_eq!(err.message, "syntax error");
414 assert!(err.detail.is_none());
415 assert!(err.hint.is_none());
416 assert!(err.position.is_none());
417 }
418
419 #[test]
420 fn test_from_fields_unknown_field_ignored() {
421 let fields = vec![
422 (b'S', "ERROR".to_string()),
423 (b'C', "42601".to_string()),
424 (b'M', "syntax error".to_string()),
425 (b'X', "unknown field".to_string()), ];
427
428 let err = PgServerError::from_fields(fields);
429 assert_eq!(err.message, "syntax error");
430 }
431
432 #[test]
433 fn test_sqlstate_classification() {
434 let mut err = PgServerError::default();
435
436 err.code = "23505".to_string();
438 assert!(err.is_unique_violation());
439 assert!(err.is_integrity_constraint_violation());
440 assert!(!err.is_syntax_error());
441
442 err.code = "42601".to_string();
444 assert!(err.is_syntax_error());
445 assert!(!err.is_integrity_constraint_violation());
446
447 err.code = "23503".to_string();
449 assert!(err.is_foreign_key_violation());
450
451 err.code = "23502".to_string();
453 assert!(err.is_not_null_violation());
454
455 err.code = "23514".to_string();
457 assert!(err.is_check_violation());
458
459 err.code = "23P01".to_string();
461 assert!(err.is_exclusion_violation());
462
463 err.code = "42501".to_string();
465 assert!(err.is_insufficient_privilege());
466
467 err.code = "42P01".to_string();
469 assert!(err.is_undefined_table());
470
471 err.code = "42703".to_string();
473 assert!(err.is_undefined_column());
474
475 err.code = "40001".to_string();
477 assert!(err.is_serialization_failure());
478
479 err.code = "40P01".to_string();
481 assert!(err.is_deadlock_detected());
482
483 err.code = "08006".to_string();
485 assert!(err.is_connection_exception());
486 assert!(err.is_connection_failure());
487
488 err.code = "57014".to_string();
490 assert!(err.is_query_canceled());
491 }
492
493 #[test]
494 fn test_severity_checks() {
495 let mut err = PgServerError::default();
496
497 err.severity = "FATAL".to_string();
498 assert!(err.is_fatal());
499 assert!(!err.is_warning_or_less());
500
501 err.severity = "PANIC".to_string();
502 assert!(err.is_fatal());
503
504 err.severity = "ERROR".to_string();
505 assert!(!err.is_fatal());
506 assert!(!err.is_warning_or_less());
507
508 err.severity = "WARNING".to_string();
509 assert!(err.is_warning_or_less());
510
511 err.severity = "DEBUG".to_string();
512 assert!(err.is_warning_or_less());
513
514 err.severity = "INFO".to_string();
515 assert!(err.is_warning_or_less());
516
517 err.severity = "LOG".to_string();
518 assert!(err.is_warning_or_less());
519 }
520
521 #[test]
522 fn test_display_format() {
523 let err = PgServerError::from_fields(vec![
524 (b'S', "ERROR".to_string()),
525 (b'C', "23505".to_string()),
526 (b'M', "duplicate key".to_string()),
527 (b'D', "Key (id)=(1) already exists.".to_string()),
528 (b'H', "Try a different value.".to_string()),
529 (b'P', "42".to_string()),
530 ]);
531
532 let display = err.to_string();
533 assert!(display.contains("ERROR: duplicate key (SQLSTATE 23505)"));
534 assert!(display.contains("DETAIL: Key (id)=(1) already exists."));
535 assert!(display.contains("HINT: Try a different value."));
536 assert!(display.contains("POSITION: 42"));
537 }
538
539 #[test]
540 fn test_display_format_minimal() {
541 let err = PgServerError::from_fields(vec![
542 (b'S', "ERROR".to_string()),
543 (b'C', "42601".to_string()),
544 (b'M', "syntax error".to_string()),
545 ]);
546
547 let display = err.to_string();
548 assert_eq!(display, "ERROR: syntax error (SQLSTATE 42601)");
549 }
550
551 #[test]
552 fn test_convenience_accessors() {
553 let err = PgServerError::from_fields(vec![
554 (b'S', "ERROR".to_string()),
555 (b'C', "23505".to_string()),
556 (b'M', "duplicate key".to_string()),
557 (b'D', "some detail".to_string()),
558 (b'H', "some hint".to_string()),
559 (b's', "public".to_string()),
560 (b't', "users".to_string()),
561 (b'c', "id".to_string()),
562 (b'n', "users_pkey".to_string()),
563 (b'P', "42".to_string()),
564 ]);
565
566 assert_eq!(err.schema(), Some("public"));
567 assert_eq!(err.table(), Some("users"));
568 assert_eq!(err.column(), Some("id"));
569 assert_eq!(err.constraint(), Some("users_pkey"));
570 assert_eq!(err.detail(), Some("some detail"));
571 assert_eq!(err.hint(), Some("some hint"));
572 assert_eq!(err.position(), Some(42));
573 }
574
575 #[test]
576 fn test_connection_related_codes() {
577 let mut err = PgServerError::default();
578
579 err.code = "08003".to_string();
580 assert!(err.is_connection_does_not_exist());
581
582 err.code = "08001".to_string();
583 assert!(err.is_sqlclient_unable_to_establish_sqlconnection());
584
585 err.code = "57P01".to_string();
586 assert!(err.is_admin_shutdown());
587
588 err.code = "57P02".to_string();
589 assert!(err.is_crash_shutdown());
590
591 err.code = "57P03".to_string();
592 assert!(err.is_cannot_connect_now());
593
594 err.code = "57P04".to_string();
595 assert!(err.is_database_dropped());
596
597 err.code = "57P05".to_string();
598 assert!(err.is_idle_session_timeout());
599 }
600}