44from codecs import lookup as lookup_codecs
55from abc import ABCMeta , abstractmethod
66from operator import itemgetter
7+ get0 = itemgetter (0 )
8+ get1 = itemgetter (1 )
79from itertools import count
810from ..encodings .aliases import get_python_name
911from ..python .functools import Composition as compose
1315from ..types import Row , Array , oid_to_sql_name , oid_to_name
1416from ..python .functools import process_tuple
1517from .. import exceptions as pg_exc
18+ from ..message import Message
1619from ..types .io import lib
20+ from ..protocol .element3 import ClientError , ClientNotice
21+ from ..protocol .message_types import message_types
22+
23+ # Map element3.Notice field identifiers
24+ # to names used by message.Message.
25+ notice_field_to_name = {
26+ message_types [b'S' [0 ]] : 'severity' ,
27+ message_types [b'C' [0 ]] : 'code' ,
28+ message_types [b'M' [0 ]] : 'message' ,
29+ message_types [b'D' [0 ]] : 'detail' ,
30+ message_types [b'H' [0 ]] : 'hint' ,
31+ message_types [b'W' [0 ]] : 'context' ,
32+ message_types [b'P' [0 ]] : 'position' ,
33+ message_types [b'p' [0 ]] : 'internal_position' ,
34+ message_types [b'q' [0 ]] : 'internal_query' ,
35+ message_types [b'F' [0 ]] : 'file' ,
36+ message_types [b'L' [0 ]] : 'line' ,
37+ message_types [b'R' [0 ]] : 'function' ,
38+ }
39+
40+ notice_field_from_name = dict (
41+ (v , k ) for (k , v ) in notice_field_to_name .items ()
42+ )
1743
1844class TypeIO (object , metaclass = ABCMeta ):
1945 """
@@ -48,13 +74,13 @@ def encode(self, string_data):
4874 def decode (self , bytes_data ):
4975 return self ._decode (bytes_data )[0 ]
5076
51- def encodes (self , iter , get0 = itemgetter ( 0 ) ):
77+ def encodes (self , iter , get0 = get0 ):
5278 """
5379 Encode the items in the iterable in the configured encoding.
5480 """
5581 return map (compose ((self ._encode , get0 )), iter )
5682
57- def decodes (self , iter , get0 = itemgetter ( 0 ) ):
83+ def decodes (self , iter , get0 = get0 ):
5884 """
5985 Decode the items in the iterable from the configured encoding.
6086 """
@@ -89,9 +115,6 @@ def __init__(self):
89115 }
90116 self .typinfo = {}
91117
92- def row_type_factory (self , column_names ):
93- pass
94-
95118 def sql_type_from_oid (self , oid , qi = quote_ident ):
96119 if oid in oid_to_sql_name :
97120 return oid_to_sql_name [oid ]
@@ -274,8 +297,9 @@ def record_io_factory(self,
274297 typnames : "sequence of sql type names in order" ,
275298 attnames : "sequence of attribute names in order" ,
276299 composite_name : "the name of the composite type" ,
277- get0 = itemgetter (0 ),
278- get1 = itemgetter (1 ),
300+ get0 = get0 ,
301+ get1 = get1 ,
302+ fmt_errmsg = "failed to {0} attribute {1}, {2}::{3}, of composite {4} from wire data" .format
279303 ):
280304 fpack = tuple (map (get0 , column_io ))
281305 funpack = tuple (map (get1 , column_io ))
@@ -285,36 +309,26 @@ def raise_pack_tuple_error(procs, tup, itemnum):
285309 if len (data ) > 80 :
286310 # Be sure not to fill screen with noise.
287311 data = data [:75 ] + ' ...'
288- raise pg_exc .ColumnError (
289- "failed to pack attribute %d, %s::%s, of composite %s for transfer" % (
290- itemnum ,
291- attnames [itemnum ],
292- typnames [itemnum ],
293- composite_name ,
294- ),
295- details = {
296- 'context' : data ,
297- 'position' : str (itemnum )
298- },
299- )
312+ self .raise_client_error (ClientError ((
313+ (b'C' , '--cIO' ,),
314+ (b'S' , 'ERROR' ,),
315+ (b'M' , fmt_errmsg ('pack' , itemnum , attnames [itemnum ], typnames [itemnum ], composite_name ),),
316+ (b'W' , data ,),
317+ (b'P' , str (itemnum ),)
318+ )))
300319
301320 def raise_unpack_tuple_error (procs , tup , itemnum ):
302321 data = repr (tup [itemnum ])
303322 if len (data ) > 80 :
304323 # Be sure not to fill screen with noise.
305324 data = data [:75 ] + ' ...'
306- raise pg_exc .ColumnError (
307- "failed to unpack attribute %d, %s::%s, of composite %s from wire data" % (
308- itemnum ,
309- attnames [itemnum ],
310- typnames [itemnum ],
311- composite_name ,
312- ),
313- details = {
314- 'context' : data ,
315- 'position' : str (itemnum ),
316- },
317- )
325+ self .raise_client_error (ClientError ((
326+ (b'C' , '--cIO' ,),
327+ (b'S' , 'ERROR' ,),
328+ (b'M' , fmt_errmsg ('unpack' , itemnum , attnames [itemnum ], typnames [itemnum ], composite_name ),),
329+ (b'W' , data ,),
330+ (b'P' , str (itemnum ),),
331+ )))
318332
319333 def unpack_a_record (data ,
320334 unpack = lib .record_unpack ,
@@ -340,3 +354,119 @@ def pack_a_record(data,
340354 ))
341355 )
342356 return (pack_a_record , unpack_a_record , Row )
357+
358+ def raise_client_error (self , error_message , cause = None , creator = None ):
359+ m = {
360+ notice_field_to_name [k ] : v
361+ for k , v in error_message .items ()
362+ # don't include unknown messages in this list.
363+ if k in notice_field_to_name
364+ }
365+ c = m .pop ('code' )
366+ ms = m .pop ('message' )
367+ client_error = self .lookup_exception (c )
368+ client_error = client_error (ms , code = c , details = m , source = 'CLIENT' , creator = creator or self .database )
369+ client_error .database = self .database
370+ if cause is not None :
371+ raise client_error from cause
372+ else :
373+ raise client_error
374+
375+ def lookup_exception (self , code , errorlookup = pg_exc .ErrorLookup ,):
376+ return errorlookup (code )
377+
378+ def lookup_warning (self , code , warninglookup = pg_exc .WarningLookup ,):
379+ return warninglookup (code )
380+
381+ def raise_server_error (self , error_message , cause = None , creator = None ):
382+ m = dict (self .decode_notice (error_message ))
383+ c = m .pop ('code' )
384+ ms = m .pop ('message' )
385+ server_error = self .lookup_exception (c )
386+ server_error = server_error (ms , code = c , details = m , source = 'SERVER' , creator = creator or self .database )
387+ server_error .database = self .database
388+ if cause is not None :
389+ raise server_error from cause
390+ else :
391+ raise server_error
392+
393+ def raise_error (self , error_message , ClientError = ClientError , ** kw ):
394+ if 'creator' not in kw :
395+ kw ['creator' ] = getattr (self .database , '_controller' , self .database ) or self .database
396+
397+ if error_message .__class__ is ClientError :
398+ self .raise_client_error (error_message , ** kw )
399+ else :
400+ self .raise_server_error (error_message , ** kw )
401+
402+ ##
403+ # Used by decode_notice()
404+ def _decode_failsafe (self , data ):
405+ decode = self ._decode
406+ i = iter (data )
407+ for x in i :
408+ try :
409+ # prematurely optimized for your viewing displeasure.
410+ v = x [1 ]
411+ yield (x [0 ], decode (v )[0 ])
412+ for x in i :
413+ v = x [1 ]
414+ yield (x [0 ], decode (v )[0 ])
415+ except UnicodeDecodeError :
416+ # Fallback to the bytes representation.
417+ # This should be sufficiently informative in most cases,
418+ # and in the cases where it isn't, an element traceback should
419+ # ultimately yield the pertinent information
420+ yield (x [0 ], repr (data [1 ])[2 :- 1 ])
421+
422+ def decode_notice (self , notice ):
423+ notice = self ._decode_failsafe (notice .items ())
424+ return {
425+ notice_field_to_name [k ] : v
426+ for k , v in notice
427+ # don't include unknown messages in this list.
428+ if k in notice_field_to_name
429+ }
430+
431+ def emit_server_message (self , message , creator = None ,
432+ MessageType = Message
433+ ):
434+ fields = self .decode_notice (message )
435+ m = fields .pop ('message' )
436+ c = fields .pop ('code' )
437+
438+ if fields ['severity' ].upper () == 'WARNING' :
439+ MessageType = self .lookup_warning (c )
440+
441+ message = Message (m , code = c , details = fields ,
442+ creator = creator , source = 'SERVER' )
443+ message .database = self .database
444+ message .emit ()
445+ return message
446+
447+ def emit_client_message (self , message , creator = None ,
448+ MessageType = Message
449+ ):
450+ fields = {
451+ notice_field_to_name [k ] : v
452+ for k , v in message .items ()
453+ # don't include unknown messages in this list.
454+ if k in notice_field_to_name
455+ }
456+ m = fields .pop ('message' )
457+ c = fields .pop ('code' )
458+
459+ if fields ['severity' ].upper () == 'WARNING' :
460+ MessageType = self .lookup_warning (c )
461+
462+ message = MessageType (m , code = c , details = fields ,
463+ creator = creator , source = 'CLIENT' )
464+ message .database = self .database
465+ message .emit ()
466+ return message
467+
468+ def emit_message (self , message , ClientNotice = ClientNotice , ** kw ):
469+ if message .__class__ is ClientNotice :
470+ return self .emit_client_message (message , ** kw )
471+ else :
472+ return self .emit_server_message (message , ** kw )
0 commit comments