Skip to content

Commit 3a8551a

Browse files
author
James William Pye
committed
Relocate all high-level transformations into typio.
This places all the error_lookup routines into db.typio. This is an important step for DB-API getting its own exception hierarchy, and for getting RowFactory implemented. Add another TypeIOError subclass for composites.
1 parent f5f2a76 commit 3a8551a

6 files changed

Lines changed: 222 additions & 213 deletions

File tree

postgresql/cluster.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,7 @@ def drop(self):
321321
'hint' : 'Shared memory may be leaked.'
322322
},
323323
creator = self
324-
).raise_message()
324+
).emit()
325325
# Really, using rm -rf would be the best, but use this for portability.
326326
for root, dirs, files in os.walk(self.data_directory, topdown = False):
327327
for name in files:

postgresql/driver/pg_type.py

Lines changed: 161 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
from codecs import lookup as lookup_codecs
55
from abc import ABCMeta, abstractmethod
66
from operator import itemgetter
7+
get0 = itemgetter(0)
8+
get1 = itemgetter(1)
79
from itertools import count
810
from ..encodings.aliases import get_python_name
911
from ..python.functools import Composition as compose
@@ -13,7 +15,31 @@
1315
from ..types import Row, Array, oid_to_sql_name, oid_to_name
1416
from ..python.functools import process_tuple
1517
from .. import exceptions as pg_exc
18+
from ..message import Message
1619
from ..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

1844
class 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

Comments
 (0)