@@ -11,10 +11,13 @@ use crate::client::PREPARED_STATEMENT_COUNTER;
11
11
use crate :: config:: get_config;
12
12
use crate :: errors:: Error ;
13
13
14
+ use crate :: constants:: MESSAGE_TERMINATOR ;
14
15
use std:: collections:: HashMap ;
15
16
use std:: ffi:: CString ;
17
+ use std:: fmt:: { Display , Formatter } ;
16
18
use std:: io:: { BufRead , Cursor } ;
17
19
use std:: mem;
20
+ use std:: str:: FromStr ;
18
21
use std:: sync:: atomic:: Ordering ;
19
22
use std:: time:: Duration ;
20
23
@@ -1098,3 +1101,298 @@ pub fn prepared_statement_name() -> String {
1098
1101
PREPARED_STATEMENT_COUNTER . fetch_add( 1 , Ordering :: SeqCst )
1099
1102
)
1100
1103
}
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