@@ -6,6 +6,7 @@ use log::{debug, error, info, trace, warn};
6
6
use parking_lot:: { Mutex , RwLock } ;
7
7
use postgres_protocol:: message;
8
8
use std:: collections:: { BTreeSet , HashMap } ;
9
+ use std:: fmt:: { Display , Formatter } ;
9
10
use std:: io:: Read ;
10
11
use std:: net:: IpAddr ;
11
12
use std:: sync:: Arc ;
@@ -25,6 +26,7 @@ use crate::pool::ClientServerMap;
25
26
use crate :: scram:: ScramSha256 ;
26
27
use crate :: stats:: ServerStats ;
27
28
use std:: io:: Write ;
29
+ use std:: str:: FromStr ;
28
30
29
31
use pin_project:: pin_project;
30
32
@@ -588,8 +590,7 @@ impl Server {
588
590
589
591
// An error message will be present.
590
592
_ => {
591
- // Read the error message without the terminating null character.
592
- let mut error = vec ! [ 0u8 ; len as usize - 4 - 1 ] ;
593
+ let mut error = vec ! [ 0u8 ; len as usize ] ;
593
594
594
595
match stream. read_exact ( & mut error) . await {
595
596
Ok ( _) => ( ) ,
@@ -601,10 +602,9 @@ impl Server {
601
602
}
602
603
} ;
603
604
604
- // TODO: the error message contains multiple fields; we can decode them and
605
- // present a prettier message to the user.
606
- // See: https://www.postgresql.org/docs/12/protocol-error-fields.html
607
- error ! ( "Server error: {}" , String :: from_utf8_lossy( & error) ) ;
605
+ let fields = PgErrorMsg :: parse ( error) ;
606
+ trace ! ( "error fields: {}" , & fields) ;
607
+ error ! ( "server error: {}: {}" , fields. severity, fields. message) ;
608
608
}
609
609
} ;
610
610
@@ -1353,3 +1353,287 @@ impl Drop for Server {
1353
1353
) ;
1354
1354
}
1355
1355
}
1356
+
1357
+ // from https://www.postgresql.org/docs/12/protocol-error-fields.html
1358
+ #[ derive( Debug , Default , PartialEq ) ]
1359
+ pub struct PgErrorMsg {
1360
+ severity_localized : String , // S
1361
+ severity : String , // V
1362
+ code : String , // C
1363
+ message : String , // M
1364
+ detail : Option < String > , // D
1365
+ hint : Option < String > , // H
1366
+ position : Option < u32 > , // P
1367
+ internal_position : Option < u32 > , // p
1368
+ internal_query : Option < String > , // q
1369
+ where_context : Option < String > , // W
1370
+ schema_name : Option < String > , // s
1371
+ table_name : Option < String > , // t
1372
+ column_name : Option < String > , // c
1373
+ data_type_name : Option < String > , // d
1374
+ constraint_name : Option < String > , // n
1375
+ file_name : Option < String > , // F
1376
+ line : Option < u32 > , // L
1377
+ routine : Option < String > , // R
1378
+ }
1379
+
1380
+ // TODO: implement with https://docs.rs/derive_more/latest/derive_more/
1381
+ impl Display for PgErrorMsg {
1382
+ fn fmt ( & self , f : & mut Formatter < ' _ > ) -> std:: fmt:: Result {
1383
+ write ! ( f, "[severity: {}]" , self . severity) ?;
1384
+ write ! ( f, "[code: {}]" , self . code) ?;
1385
+ write ! ( f, "[message: {}]" , self . message) ?;
1386
+ if let Some ( val) = & self . detail {
1387
+ write ! ( f, "[detail: {val}]" ) ?;
1388
+ }
1389
+ if let Some ( val) = & self . hint {
1390
+ write ! ( f, "[hint: {val}]" ) ?;
1391
+ }
1392
+ if let Some ( val) = & self . position {
1393
+ write ! ( f, "[position: {val}]" ) ?;
1394
+ }
1395
+ if let Some ( val) = & self . internal_position {
1396
+ write ! ( f, "[internal_position: {val}]" ) ?;
1397
+ }
1398
+ if let Some ( val) = & self . internal_query {
1399
+ write ! ( f, "[internal_query: {val}]" ) ?;
1400
+ }
1401
+ if let Some ( val) = & self . internal_query {
1402
+ write ! ( f, "[internal_query: {val}]" ) ?;
1403
+ }
1404
+ if let Some ( val) = & self . where_context {
1405
+ write ! ( f, "[where: {val}]" ) ?;
1406
+ }
1407
+ if let Some ( val) = & self . schema_name {
1408
+ write ! ( f, "[schema_name: {val}]" ) ?;
1409
+ }
1410
+ if let Some ( val) = & self . table_name {
1411
+ write ! ( f, "[table_name: {val}]" ) ?;
1412
+ }
1413
+ if let Some ( val) = & self . column_name {
1414
+ write ! ( f, "[column_name: {val}]" ) ?;
1415
+ }
1416
+ if let Some ( val) = & self . data_type_name {
1417
+ write ! ( f, "[data_type_name: {val}]" ) ?;
1418
+ }
1419
+ if let Some ( val) = & self . constraint_name {
1420
+ write ! ( f, "[constraint_name: {val}]" ) ?;
1421
+ }
1422
+ if let Some ( val) = & self . file_name {
1423
+ write ! ( f, "[file_name: {val}]" ) ?;
1424
+ }
1425
+ if let Some ( val) = & self . line {
1426
+ write ! ( f, "[line: {val}]" ) ?;
1427
+ }
1428
+ if let Some ( val) = & self . routine {
1429
+ write ! ( f, "[routine: {val}]" ) ?;
1430
+ }
1431
+
1432
+ write ! ( f, " " ) ?;
1433
+
1434
+ Ok ( ( ) )
1435
+ }
1436
+ }
1437
+
1438
+ impl PgErrorMsg {
1439
+ pub fn parse ( error_msg : Vec < u8 > ) -> PgErrorMsg {
1440
+ let mut out = PgErrorMsg {
1441
+ severity_localized : "" . to_string ( ) ,
1442
+ severity : "" . to_string ( ) ,
1443
+ code : "" . to_string ( ) ,
1444
+ message : "" . to_string ( ) ,
1445
+ detail : None ,
1446
+ hint : None ,
1447
+ position : None ,
1448
+ internal_position : None ,
1449
+ internal_query : None ,
1450
+ where_context : None ,
1451
+ schema_name : None ,
1452
+ table_name : None ,
1453
+ column_name : None ,
1454
+ data_type_name : None ,
1455
+ constraint_name : None ,
1456
+ file_name : None ,
1457
+ line : None ,
1458
+ routine : None ,
1459
+ } ;
1460
+ for msg_part in error_msg. split ( |v| * v == MESSAGE_TERMINATOR ) {
1461
+ if msg_part. is_empty ( ) {
1462
+ continue ;
1463
+ }
1464
+
1465
+ let msg_content = String :: from_utf8_lossy ( & msg_part[ 1 ..] ) . parse ( ) . unwrap ( ) ;
1466
+
1467
+ match & msg_part[ 0 ] {
1468
+ b'S' => {
1469
+ out. severity_localized = msg_content;
1470
+ }
1471
+ b'V' => {
1472
+ out. severity = msg_content;
1473
+ }
1474
+ b'C' => {
1475
+ out. code = msg_content;
1476
+ }
1477
+ b'M' => {
1478
+ out. message = msg_content;
1479
+ }
1480
+ b'D' => {
1481
+ out. detail = Some ( msg_content) ;
1482
+ }
1483
+ b'H' => {
1484
+ out. hint = Some ( msg_content) ;
1485
+ }
1486
+ b'P' => out. position = Some ( u32:: from_str ( msg_content. as_str ( ) ) . unwrap_or ( 0 ) ) ,
1487
+ b'p' => {
1488
+ out. internal_position = Some ( u32:: from_str ( msg_content. as_str ( ) ) . unwrap_or ( 0 ) )
1489
+ }
1490
+ b'q' => {
1491
+ out. internal_query = Some ( msg_content) ;
1492
+ }
1493
+ b'W' => {
1494
+ out. where_context = Some ( msg_content) ;
1495
+ }
1496
+ b's' => {
1497
+ out. schema_name = Some ( msg_content) ;
1498
+ }
1499
+ b't' => {
1500
+ out. table_name = Some ( msg_content) ;
1501
+ }
1502
+ b'c' => {
1503
+ out. column_name = Some ( msg_content) ;
1504
+ }
1505
+ b'd' => {
1506
+ out. data_type_name = Some ( msg_content) ;
1507
+ }
1508
+ b'n' => {
1509
+ out. constraint_name = Some ( msg_content) ;
1510
+ }
1511
+ b'F' => {
1512
+ out. file_name = Some ( msg_content) ;
1513
+ }
1514
+ b'L' => out. line = Some ( u32:: from_str ( msg_content. as_str ( ) ) . unwrap_or ( 0 ) ) ,
1515
+ b'R' => {
1516
+ out. routine = Some ( msg_content) ;
1517
+ }
1518
+ _ => { }
1519
+ }
1520
+ }
1521
+
1522
+ out
1523
+ }
1524
+ }
1525
+
1526
+ #[ cfg( test) ]
1527
+ mod tests {
1528
+ use crate :: server:: PgErrorMsg ;
1529
+ use log:: { error, info} ;
1530
+
1531
+ fn field ( kind : char , content : & str ) -> Vec < u8 > {
1532
+ format ! ( "{kind}{content}\0 " ) . as_bytes ( ) . to_vec ( )
1533
+ }
1534
+
1535
+ #[ test]
1536
+ fn parse_fields ( ) {
1537
+ let mut complete_msg = vec ! [ ] ;
1538
+ let severity = "FATAL" ;
1539
+ complete_msg. extend ( field ( 'S' , & severity) ) ;
1540
+ complete_msg. extend ( field ( 'V' , & severity) ) ;
1541
+
1542
+ let error_code = "29P02" ;
1543
+ complete_msg. extend ( field ( 'C' , & error_code) ) ;
1544
+ let message = "password authentication failed for user \" wrong_user\" " ;
1545
+ complete_msg. extend ( field ( 'M' , & message) ) ;
1546
+ let detail_msg = "super detailed message" ;
1547
+ complete_msg. extend ( field ( 'D' , & detail_msg) ) ;
1548
+ let hint_msg = "hint detail here" ;
1549
+ complete_msg. extend ( field ( 'H' , & hint_msg) ) ;
1550
+ complete_msg. extend ( field ( 'P' , "123" ) ) ;
1551
+ complete_msg. extend ( field ( 'p' , "234" ) ) ;
1552
+ let internal_query = "SELECT * from foo;" ;
1553
+ complete_msg. extend ( field ( 'q' , & internal_query) ) ;
1554
+ let where_msg = "where goes here" ;
1555
+ complete_msg. extend ( field ( 'W' , & where_msg) ) ;
1556
+ let schema_msg = "schema_name" ;
1557
+ complete_msg. extend ( field ( 's' , & schema_msg) ) ;
1558
+ let table_msg = "table_name" ;
1559
+ complete_msg. extend ( field ( 't' , & table_msg) ) ;
1560
+ let column_msg = "column_name" ;
1561
+ complete_msg. extend ( field ( 'c' , & column_msg) ) ;
1562
+ let data_type_msg = "type_name" ;
1563
+ complete_msg. extend ( field ( 'd' , & data_type_msg) ) ;
1564
+ let constraint_msg = "constraint_name" ;
1565
+ complete_msg. extend ( field ( 'n' , & constraint_msg) ) ;
1566
+ let file_msg = "pgcat.c" ;
1567
+ complete_msg. extend ( field ( 'F' , & file_msg) ) ;
1568
+ complete_msg. extend ( field ( 'L' , "335" ) ) ;
1569
+ let routine_msg = "my_failing_routine" ;
1570
+ complete_msg. extend ( field ( 'R' , & routine_msg) ) ;
1571
+
1572
+ tracing_subscriber:: fmt ( )
1573
+ . with_max_level ( tracing:: Level :: INFO )
1574
+ . with_ansi ( true )
1575
+ . init ( ) ;
1576
+
1577
+ info ! ( "full message: {}" , PgErrorMsg :: parse( complete_msg. clone( ) ) ) ;
1578
+ assert_eq ! (
1579
+ PgErrorMsg {
1580
+ severity_localized: severity. to_string( ) ,
1581
+ severity: severity. to_string( ) ,
1582
+ code: error_code. to_string( ) ,
1583
+ message: message. to_string( ) ,
1584
+ detail: Some ( detail_msg. to_string( ) ) ,
1585
+ hint: Some ( hint_msg. to_string( ) ) ,
1586
+ position: Some ( 123 ) ,
1587
+ internal_position: Some ( 234 ) ,
1588
+ internal_query: Some ( internal_query. to_string( ) ) ,
1589
+ where_context: Some ( where_msg. to_string( ) ) ,
1590
+ schema_name: Some ( schema_msg. to_string( ) ) ,
1591
+ table_name: Some ( table_msg. to_string( ) ) ,
1592
+ column_name: Some ( column_msg. to_string( ) ) ,
1593
+ data_type_name: Some ( data_type_msg. to_string( ) ) ,
1594
+ constraint_name: Some ( constraint_msg. to_string( ) ) ,
1595
+ file_name: Some ( file_msg. to_string( ) ) ,
1596
+ line: Some ( 335 ) ,
1597
+ routine: Some ( routine_msg. to_string( ) ) ,
1598
+ } ,
1599
+ PgErrorMsg :: parse( complete_msg)
1600
+ ) ;
1601
+
1602
+ let mut only_mandatory_msg = vec ! [ ] ;
1603
+ only_mandatory_msg. extend ( field ( 'S' , & severity) ) ;
1604
+ only_mandatory_msg. extend ( field ( 'V' , & severity) ) ;
1605
+ only_mandatory_msg. extend ( field ( 'C' , & error_code) ) ;
1606
+ only_mandatory_msg. extend ( field ( 'M' , & message) ) ;
1607
+ only_mandatory_msg. extend ( field ( 'D' , & detail_msg) ) ;
1608
+
1609
+ let err_fields = PgErrorMsg :: parse ( only_mandatory_msg. clone ( ) ) ;
1610
+ info ! ( "only mandatory fields: {}" , & err_fields) ;
1611
+ error ! (
1612
+ "server error: {}: {}" ,
1613
+ err_fields. severity, err_fields. message
1614
+ ) ;
1615
+ assert_eq ! (
1616
+ PgErrorMsg {
1617
+ severity_localized: severity. to_string( ) ,
1618
+ severity: severity. to_string( ) ,
1619
+ code: error_code. to_string( ) ,
1620
+ message: message. to_string( ) ,
1621
+ detail: Some ( detail_msg. to_string( ) ) ,
1622
+ hint: None ,
1623
+ position: None ,
1624
+ internal_position: None ,
1625
+ internal_query: None ,
1626
+ where_context: None ,
1627
+ schema_name: None ,
1628
+ table_name: None ,
1629
+ column_name: None ,
1630
+ data_type_name: None ,
1631
+ constraint_name: None ,
1632
+ file_name: None ,
1633
+ line: None ,
1634
+ routine: None ,
1635
+ } ,
1636
+ PgErrorMsg :: parse( only_mandatory_msg)
1637
+ ) ;
1638
+ }
1639
+ }
0 commit comments