@@ -11,10 +11,13 @@ use crate::client::PREPARED_STATEMENT_COUNTER;
1111use crate :: config:: get_config;
1212use crate :: errors:: Error ;
1313
14+ use crate :: constants:: MESSAGE_TERMINATOR ;
1415use std:: collections:: HashMap ;
1516use std:: ffi:: CString ;
17+ use std:: fmt:: { Display , Formatter } ;
1618use std:: io:: { BufRead , Cursor } ;
1719use std:: mem;
20+ use std:: str:: FromStr ;
1821use std:: sync:: atomic:: Ordering ;
1922use std:: time:: Duration ;
2023
@@ -1098,3 +1101,298 @@ pub fn prepared_statement_name() -> String {
10981101 PREPARED_STATEMENT_COUNTER . fetch_add( 1 , Ordering :: SeqCst )
10991102 )
11001103}
1104+
1105+ // from https://www.postgresql.org/docs/12/protocol-error-fields.html
1106+ #[ derive( Debug , Default , PartialEq ) ]
1107+ pub struct PgErrorMsg {
1108+ pub severity_localized : String , // S
1109+ pub severity : String , // V
1110+ pub code : String , // C
1111+ pub message : String , // M
1112+ pub detail : Option < String > , // D
1113+ pub hint : Option < String > , // H
1114+ pub position : Option < u32 > , // P
1115+ pub internal_position : Option < u32 > , // p
1116+ pub internal_query : Option < String > , // q
1117+ pub where_context : Option < String > , // W
1118+ pub schema_name : Option < String > , // s
1119+ pub table_name : Option < String > , // t
1120+ pub column_name : Option < String > , // c
1121+ pub data_type_name : Option < String > , // d
1122+ pub constraint_name : Option < String > , // n
1123+ pub file_name : Option < String > , // F
1124+ pub line : Option < u32 > , // L
1125+ pub routine : Option < String > , // R
1126+ }
1127+
1128+ // TODO: implement with https://docs.rs/derive_more/latest/derive_more/
1129+ impl Display for PgErrorMsg {
1130+ fn fmt ( & self , f : & mut Formatter < ' _ > ) -> std:: fmt:: Result {
1131+ write ! ( f, "[severity: {}]" , self . severity) ?;
1132+ write ! ( f, "[code: {}]" , self . code) ?;
1133+ write ! ( f, "[message: {}]" , self . message) ?;
1134+ if let Some ( val) = & self . detail {
1135+ write ! ( f, "[detail: {val}]" ) ?;
1136+ }
1137+ if let Some ( val) = & self . hint {
1138+ write ! ( f, "[hint: {val}]" ) ?;
1139+ }
1140+ if let Some ( val) = & self . position {
1141+ write ! ( f, "[position: {val}]" ) ?;
1142+ }
1143+ if let Some ( val) = & self . internal_position {
1144+ write ! ( f, "[internal_position: {val}]" ) ?;
1145+ }
1146+ if let Some ( val) = & self . internal_query {
1147+ write ! ( f, "[internal_query: {val}]" ) ?;
1148+ }
1149+ if let Some ( val) = & self . internal_query {
1150+ write ! ( f, "[internal_query: {val}]" ) ?;
1151+ }
1152+ if let Some ( val) = & self . where_context {
1153+ write ! ( f, "[where: {val}]" ) ?;
1154+ }
1155+ if let Some ( val) = & self . schema_name {
1156+ write ! ( f, "[schema_name: {val}]" ) ?;
1157+ }
1158+ if let Some ( val) = & self . table_name {
1159+ write ! ( f, "[table_name: {val}]" ) ?;
1160+ }
1161+ if let Some ( val) = & self . column_name {
1162+ write ! ( f, "[column_name: {val}]" ) ?;
1163+ }
1164+ if let Some ( val) = & self . data_type_name {
1165+ write ! ( f, "[data_type_name: {val}]" ) ?;
1166+ }
1167+ if let Some ( val) = & self . constraint_name {
1168+ write ! ( f, "[constraint_name: {val}]" ) ?;
1169+ }
1170+ if let Some ( val) = & self . file_name {
1171+ write ! ( f, "[file_name: {val}]" ) ?;
1172+ }
1173+ if let Some ( val) = & self . line {
1174+ write ! ( f, "[line: {val}]" ) ?;
1175+ }
1176+ if let Some ( val) = & self . routine {
1177+ write ! ( f, "[routine: {val}]" ) ?;
1178+ }
1179+
1180+ write ! ( f, " " ) ?;
1181+
1182+ Ok ( ( ) )
1183+ }
1184+ }
1185+
1186+ impl PgErrorMsg {
1187+ pub fn parse ( error_msg : Vec < u8 > ) -> Result < PgErrorMsg , Error > {
1188+ let mut out = PgErrorMsg {
1189+ severity_localized : "" . to_string ( ) ,
1190+ severity : "" . to_string ( ) ,
1191+ code : "" . to_string ( ) ,
1192+ message : "" . to_string ( ) ,
1193+ detail : None ,
1194+ hint : None ,
1195+ position : None ,
1196+ internal_position : None ,
1197+ internal_query : None ,
1198+ where_context : None ,
1199+ schema_name : None ,
1200+ table_name : None ,
1201+ column_name : None ,
1202+ data_type_name : None ,
1203+ constraint_name : None ,
1204+ file_name : None ,
1205+ line : None ,
1206+ routine : None ,
1207+ } ;
1208+ for msg_part in error_msg. split ( |v| * v == MESSAGE_TERMINATOR ) {
1209+ if msg_part. is_empty ( ) {
1210+ continue ;
1211+ }
1212+
1213+ let msg_content = match String :: from_utf8_lossy ( & msg_part[ 1 ..] ) . parse ( ) {
1214+ Ok ( c) => c,
1215+ Err ( err) => {
1216+ return Err ( Error :: ServerMessageParserError ( format ! (
1217+ "could not parse server message field. err {:?}" ,
1218+ err
1219+ ) ) )
1220+ }
1221+ } ;
1222+
1223+ match & msg_part[ 0 ] {
1224+ b'S' => {
1225+ out. severity_localized = msg_content;
1226+ }
1227+ b'V' => {
1228+ out. severity = msg_content;
1229+ }
1230+ b'C' => {
1231+ out. code = msg_content;
1232+ }
1233+ b'M' => {
1234+ out. message = msg_content;
1235+ }
1236+ b'D' => {
1237+ out. detail = Some ( msg_content) ;
1238+ }
1239+ b'H' => {
1240+ out. hint = Some ( msg_content) ;
1241+ }
1242+ b'P' => out. position = Some ( u32:: from_str ( msg_content. as_str ( ) ) . unwrap_or ( 0 ) ) ,
1243+ b'p' => {
1244+ out. internal_position = Some ( u32:: from_str ( msg_content. as_str ( ) ) . unwrap_or ( 0 ) )
1245+ }
1246+ b'q' => {
1247+ out. internal_query = Some ( msg_content) ;
1248+ }
1249+ b'W' => {
1250+ out. where_context = Some ( msg_content) ;
1251+ }
1252+ b's' => {
1253+ out. schema_name = Some ( msg_content) ;
1254+ }
1255+ b't' => {
1256+ out. table_name = Some ( msg_content) ;
1257+ }
1258+ b'c' => {
1259+ out. column_name = Some ( msg_content) ;
1260+ }
1261+ b'd' => {
1262+ out. data_type_name = Some ( msg_content) ;
1263+ }
1264+ b'n' => {
1265+ out. constraint_name = Some ( msg_content) ;
1266+ }
1267+ b'F' => {
1268+ out. file_name = Some ( msg_content) ;
1269+ }
1270+ b'L' => out. line = Some ( u32:: from_str ( msg_content. as_str ( ) ) . unwrap_or ( 0 ) ) ,
1271+ b'R' => {
1272+ out. routine = Some ( msg_content) ;
1273+ }
1274+ _ => { }
1275+ }
1276+ }
1277+
1278+ Ok ( out)
1279+ }
1280+ }
1281+
1282+ #[ cfg( test) ]
1283+ mod tests {
1284+ use crate :: messages:: PgErrorMsg ;
1285+ use log:: { error, info} ;
1286+
1287+ fn field ( kind : char , content : & str ) -> Vec < u8 > {
1288+ format ! ( "{kind}{content}\0 " ) . as_bytes ( ) . to_vec ( )
1289+ }
1290+
1291+ #[ test]
1292+ fn parse_fields ( ) {
1293+ let mut complete_msg = vec ! [ ] ;
1294+ let severity = "FATAL" ;
1295+ complete_msg. extend ( field ( 'S' , & severity) ) ;
1296+ complete_msg. extend ( field ( 'V' , & severity) ) ;
1297+
1298+ let error_code = "29P02" ;
1299+ complete_msg. extend ( field ( 'C' , & error_code) ) ;
1300+ let message = "password authentication failed for user \" wrong_user\" " ;
1301+ complete_msg. extend ( field ( 'M' , & message) ) ;
1302+ let detail_msg = "super detailed message" ;
1303+ complete_msg. extend ( field ( 'D' , & detail_msg) ) ;
1304+ let hint_msg = "hint detail here" ;
1305+ complete_msg. extend ( field ( 'H' , & hint_msg) ) ;
1306+ complete_msg. extend ( field ( 'P' , "123" ) ) ;
1307+ complete_msg. extend ( field ( 'p' , "234" ) ) ;
1308+ let internal_query = "SELECT * from foo;" ;
1309+ complete_msg. extend ( field ( 'q' , & internal_query) ) ;
1310+ let where_msg = "where goes here" ;
1311+ complete_msg. extend ( field ( 'W' , & where_msg) ) ;
1312+ let schema_msg = "schema_name" ;
1313+ complete_msg. extend ( field ( 's' , & schema_msg) ) ;
1314+ let table_msg = "table_name" ;
1315+ complete_msg. extend ( field ( 't' , & table_msg) ) ;
1316+ let column_msg = "column_name" ;
1317+ complete_msg. extend ( field ( 'c' , & column_msg) ) ;
1318+ let data_type_msg = "type_name" ;
1319+ complete_msg. extend ( field ( 'd' , & data_type_msg) ) ;
1320+ let constraint_msg = "constraint_name" ;
1321+ complete_msg. extend ( field ( 'n' , & constraint_msg) ) ;
1322+ let file_msg = "pgcat.c" ;
1323+ complete_msg. extend ( field ( 'F' , & file_msg) ) ;
1324+ complete_msg. extend ( field ( 'L' , "335" ) ) ;
1325+ let routine_msg = "my_failing_routine" ;
1326+ complete_msg. extend ( field ( 'R' , & routine_msg) ) ;
1327+
1328+ tracing_subscriber:: fmt ( )
1329+ . with_max_level ( tracing:: Level :: INFO )
1330+ . with_ansi ( true )
1331+ . init ( ) ;
1332+
1333+ info ! (
1334+ "full message: {}" ,
1335+ PgErrorMsg :: parse( complete_msg. clone( ) ) . unwrap( )
1336+ ) ;
1337+ assert_eq ! (
1338+ PgErrorMsg {
1339+ severity_localized: severity. to_string( ) ,
1340+ severity: severity. to_string( ) ,
1341+ code: error_code. to_string( ) ,
1342+ message: message. to_string( ) ,
1343+ detail: Some ( detail_msg. to_string( ) ) ,
1344+ hint: Some ( hint_msg. to_string( ) ) ,
1345+ position: Some ( 123 ) ,
1346+ internal_position: Some ( 234 ) ,
1347+ internal_query: Some ( internal_query. to_string( ) ) ,
1348+ where_context: Some ( where_msg. to_string( ) ) ,
1349+ schema_name: Some ( schema_msg. to_string( ) ) ,
1350+ table_name: Some ( table_msg. to_string( ) ) ,
1351+ column_name: Some ( column_msg. to_string( ) ) ,
1352+ data_type_name: Some ( data_type_msg. to_string( ) ) ,
1353+ constraint_name: Some ( constraint_msg. to_string( ) ) ,
1354+ file_name: Some ( file_msg. to_string( ) ) ,
1355+ line: Some ( 335 ) ,
1356+ routine: Some ( routine_msg. to_string( ) ) ,
1357+ } ,
1358+ PgErrorMsg :: parse( complete_msg) . unwrap( )
1359+ ) ;
1360+
1361+ let mut only_mandatory_msg = vec ! [ ] ;
1362+ only_mandatory_msg. extend ( field ( 'S' , & severity) ) ;
1363+ only_mandatory_msg. extend ( field ( 'V' , & severity) ) ;
1364+ only_mandatory_msg. extend ( field ( 'C' , & error_code) ) ;
1365+ only_mandatory_msg. extend ( field ( 'M' , & message) ) ;
1366+ only_mandatory_msg. extend ( field ( 'D' , & detail_msg) ) ;
1367+
1368+ let err_fields = PgErrorMsg :: parse ( only_mandatory_msg. clone ( ) ) . unwrap ( ) ;
1369+ info ! ( "only mandatory fields: {}" , & err_fields) ;
1370+ error ! (
1371+ "server error: {}: {}" ,
1372+ err_fields. severity, err_fields. message
1373+ ) ;
1374+ assert_eq ! (
1375+ PgErrorMsg {
1376+ severity_localized: severity. to_string( ) ,
1377+ severity: severity. to_string( ) ,
1378+ code: error_code. to_string( ) ,
1379+ message: message. to_string( ) ,
1380+ detail: Some ( detail_msg. to_string( ) ) ,
1381+ hint: None ,
1382+ position: None ,
1383+ internal_position: None ,
1384+ internal_query: None ,
1385+ where_context: None ,
1386+ schema_name: None ,
1387+ table_name: None ,
1388+ column_name: None ,
1389+ data_type_name: None ,
1390+ constraint_name: None ,
1391+ file_name: None ,
1392+ line: None ,
1393+ routine: None ,
1394+ } ,
1395+ PgErrorMsg :: parse( only_mandatory_msg) . unwrap( )
1396+ ) ;
1397+ }
1398+ }
0 commit comments