Skip to content

Commit ec880f6

Browse files
author
James William Pye
committed
Generalize exception instantiation in pq3.
This is being done with the purpose of allowing the error class to be defined by the driver's selected TypeIO. Currently, it's directly specified by the Connection as there are still places that rely on postgresql.exceptions being raised. Also, use ClientError's POSITION to indicate which attribute failed to pack or unpack in Type I/O error.
1 parent bc7612e commit ec880f6

3 files changed

Lines changed: 104 additions & 110 deletions

File tree

postgresql/driver/pq3.py

Lines changed: 93 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -257,22 +257,23 @@ def _raise_column_tuple_error(self, procs, tup, itemnum):
257257
if len(data) > 80:
258258
# Be sure not to fill screen with noise.
259259
data = data[:75] + ' ...'
260-
te = pg_exc.ColumnError(
261-
"failed to unpack column %r, %s::%s, from wire data" %(
260+
261+
em = element.ClientError(
262+
code = "--CIO",
263+
message = "failed to unpack column %r, %s::%s, from wire data" %(
262264
itemnum,
263265
self.column_names[itemnum],
264266
self.database.typio.sql_type_from_oid(
265267
self.statement.pg_column_types[itemnum]
266268
) or '<unknown>',
267269
),
268-
details = {
269-
'data': data,
270-
'hint' : "Try casting the column to 'text'."
271-
},
272-
creator = self
270+
detail = data,
271+
hint = "Try casting the column to 'text'.",
272+
position = str(itemnum),
273273
)
274-
te.index = itemnum
275-
raise te
274+
self.database._raise_a_pq_error(em, controller = self)
275+
# "can't happen"
276+
raise RuntimeError("failed to raise client error")
276277

277278
@property
278279
def state(self):
@@ -708,18 +709,17 @@ def _raise_parameter_tuple_error(self, procs, tup, itemnum):
708709
if len(data) > 80:
709710
# Be sure not to fill screen with noise.
710711
data = data[:75] + ' ...'
711-
te = pg_exc.ParameterError(
712-
"failed to pack parameter %s::%s for transfer" %(
712+
em = element.ClientError(
713+
message = "failed to pack parameter %s::%s for transfer" %(
713714
('$' + str(itemnum + 1)), typ,
714715
),
715-
details = {
716-
'detail': data,
717-
'hint' : "Try casting the parameter to 'text', then to the target type."
718-
},
719-
creator = self
716+
code = '--PIO',
717+
detail = data,
718+
hint = "Try casting the parameter to 'text', then to the target type.",
719+
position = str(itemnum)
720720
)
721-
te.index = itemnum
722-
raise te
721+
self.database._raise_a_pq_error(em, controller = self)
722+
raise RuntimeError("failed to raise client error")
723723

724724
def _raise_column_tuple_error(self, procs, tup, itemnum):
725725
typ = self.database.typio.sql_type_from_oid(
@@ -730,18 +730,19 @@ def _raise_column_tuple_error(self, procs, tup, itemnum):
730730
if len(data) > 80:
731731
# Be sure not to fill screen with noise.
732732
data = data[:75] + ' ...'
733-
te = pg_exc.ColumnError(
734-
"failed to unpack column %r, %s::%s, from wire data" %(
733+
734+
em = element.ClientError(
735+
message = "failed to unpack column %r, %s::%s, from wire data" %(
735736
itemnum, self.column_names[itemnum], typ
736737
),
737-
details = {
738-
'data': data,
739-
'hint' : "Try casting the column to 'text'."
740-
},
741-
creator = self
738+
code = "--CIO",
739+
detail = data,
740+
hint = "Try casting the column to 'text'.",
741+
position = str(itemnum),
742742
)
743-
te.index = itemnum
744-
raise te
743+
self.database._raise_a_pq_error(em, controller = self)
744+
# "can't happen"
745+
raise RuntimeError("failed to raise client error")
745746

746747
@property
747748
def state(self) -> str:
@@ -1053,11 +1054,14 @@ def _load_copy_chunks(self, chunks):
10531054
break
10541055
else:
10551056
# Oh, it's not a COPY at all.
1056-
e = pg_exc.OperationError(
1057-
"_load_copy_chunks() used on a non-COPY FROM STDIN query",
1058-
creator = self
1057+
x.error_message = element.ClientError(
1058+
# OperationError
1059+
code = '--OPE',
1060+
message = "_load_copy_chunks() used on a non-COPY FROM STDIN query",
10591061
)
1060-
raise e
1062+
x.fatal = False
1063+
self.database._raise_pq_error(x, controller = self)
1064+
raise RuntimeError("failed to raise client error")
10611065

10621066
for chunk in chunks:
10631067
x.messages = list(chunk)
@@ -1432,19 +1436,15 @@ def __exit__(self, typ, value, tb):
14321436
if typ is None:
14331437
# No exception, but in a failed transaction?
14341438
if self.database.pq.state == b'E':
1435-
err = pg_exc.InFailedTransactionError(
1436-
"invalid transaction block exit detected",
1437-
source = 'CLIENT',
1438-
details = {
1439-
'cause': \
1440-
'Database was in an error-state, ' \
1441-
'but no exception was raised.'
1442-
},
1443-
creator = self
1444-
)
14451439
if not self.database.closed:
14461440
self.rollback()
1447-
raise err
1441+
# pg_exc.InFailedTransactionError
1442+
em = element.ClientError(
1443+
code = '25P02',
1444+
message = 'invalid transaction block exit detected',
1445+
hint = "Database was in an error-state, but no exception was raised."
1446+
)
1447+
self.database._raise_a_pq_error(em, self)
14481448
else:
14491449
# No exception, and no error state. Everything is good.
14501450
try:
@@ -1501,15 +1501,13 @@ def start(self):
15011501
if self.state == 'open':
15021502
return
15031503
if self.state != 'initialized':
1504-
err = pg_exc.OperationError(
1505-
"transactions cannot be restarted",
1506-
details = {
1507-
'hint': \
1504+
em = element.ClientError(
1505+
code = '--OPE',
1506+
message = "transactions cannot be restarted",
1507+
hint = \
15081508
'Create a new transaction object instead of re-using an old one.'
1509-
},
1510-
creator = self
15111509
)
1512-
raise err
1510+
self.database._raise_a_pq_error(em, self)
15131511

15141512
if self.database.pq.state == b'I':
15151513
self.type = 'block'
@@ -1520,14 +1518,12 @@ def start(self):
15201518
else:
15211519
self.type = 'savepoint'
15221520
if (self.gid, self.isolation, self.mode) != (None,None,None):
1523-
err = pg_exc.OperationError(
1524-
"configured transaction used inside a transaction block",
1525-
details = {
1526-
'cause': 'A transaction block was already started.'
1527-
},
1528-
creator = self
1521+
em = element.ClientError(
1522+
code = '--OPE',
1523+
message = "configured transaction used inside a transaction block",
1524+
hint = 'A transaction block was already started.',
15291525
)
1530-
raise err
1526+
self.database._raise_a_pq_error(em, self)
15311527
q = self._savepoint_xact_string(hex(id(self)))
15321528
self.database.execute(q)
15331529
self.state = 'open'
@@ -1547,48 +1543,47 @@ def prepare(self):
15471543
if self.state == 'prepared':
15481544
return
15491545
if self.state != 'open':
1550-
err = pg_exc.OperationError(
1551-
"transaction state must be 'open' in order to prepare",
1552-
creator = self
1546+
em = element.ClientError(
1547+
code = '--OPE',
1548+
message = "transaction state must be 'open' in order to prepare",
15531549
)
1554-
raise err
1550+
self.database._raise_a_pq_error(em, self)
15551551
if self.type != 'block':
1556-
err = pg_exc.OperationError(
1557-
"improper transaction type to prepare",
1558-
creator = self
1552+
em = element.ClientError(
1553+
code = '--OPE',
1554+
message = "improper transaction type to prepare",
15591555
)
1560-
raise err
1556+
self.database._raise_a_pq_error(em, self)
15611557
q = self._prepare_string(self.gid)
15621558
self.database.execute(q)
15631559
self.state = 'prepared'
15641560

15651561
def recover(self):
15661562
if self.state != 'initialized':
1567-
err = pg_exc.OperationError(
1568-
"improper state for prepared transaction recovery",
1569-
creator = self
1563+
em = element.ClientError(
1564+
code = '--OPE',
1565+
message = "improper state for prepared transaction recovery",
15701566
)
1571-
raise err
1567+
self.database._raise_a_pq_error(em, self)
15721568
if self.database.sys.xact_is_prepared(self.gid):
15731569
self.state = 'prepared'
15741570
self.type = 'block'
15751571
else:
1576-
err = pg_exc.UndefinedObjectError(
1577-
"prepared transaction does not exist",
1578-
source = 'CLIENT',
1579-
creator = self
1572+
em = element.ClientError(
1573+
code = '42704', # UndefinedObjectError
1574+
message = "prepared transaction does not exist",
15801575
)
1581-
raise err
1576+
self.database._raise_a_pq_error(em, self)
15821577

15831578
def commit(self):
15841579
if self.state == 'committed':
15851580
return
15861581
if self.state not in ('prepared', 'open'):
1587-
err = pg_exc.OperationError(
1588-
"commit attempted on transaction with unexpected state, " + repr(self.state),
1589-
creator = self
1582+
em = element.ClientError(
1583+
code = '--OPE',
1584+
message = "commit attempted on transaction with unexpected state, " + repr(self.state),
15901585
)
1591-
raise err
1586+
self.database._raise_a_pq_error(em, self)
15921587

15931588
if self.type == 'block':
15941589
if self.gid is not None:
@@ -1598,14 +1593,11 @@ def commit(self):
15981593
q = 'COMMIT'
15991594
else:
16001595
if self.gid is not None:
1601-
err = pg_exc.OperationError(
1602-
"savepoint configured with global identifier",
1603-
details = {
1604-
'cause': "Prepared transaction started inside transaction block?"
1605-
},
1606-
creator = self
1596+
em = element.ClientError(
1597+
code = '--OPE',
1598+
message = "savepoint configured with global identifier",
16071599
)
1608-
raise err
1600+
self.database._raise_a_pq_error(em, self)
16091601
q = self._release_string(hex(id(self)))
16101602
self.database.execute(q)
16111603
self.state = 'committed'
@@ -1618,12 +1610,12 @@ def rollback(self):
16181610
if self.state == 'aborted':
16191611
return
16201612
if self.state not in ('prepared', 'open'):
1621-
err = pg_exc.OperationError(
1622-
"aborted attempted on transaction with unexpected state, " \
1623-
+ repr(self.state),
1624-
creator = self
1613+
em = element.ClientError(
1614+
code = '--OPE',
1615+
message = "ABORT attempted on transaction with unexpected state, " \
1616+
+ repr(self.state),
16251617
)
1626-
raise err
1618+
self.database._raise_a_pq_error(em, self)
16271619

16281620
if self.type == 'block':
16291621
if self.state == 'prepared':
@@ -1938,23 +1930,22 @@ def connect(self):
19381930
self.client_address = ca
19391931
self.client_port = r.get('client_port')
19401932
self.backend_start = r.get('backend_start')
1941-
try:
1942-
scstr = self.settings.cache.get('standard_conforming_strings')
1943-
if scstr is None or scstr.lower() not in ('on','true','yes'):
1944-
self.settings['standard_conforming_strings'] = str(True)
1945-
except (
1946-
pg_exc.UndefinedObjectError,
1947-
pg_exc.ImmutableRuntimeParameterError
1948-
):
1933+
##
1934+
# Set standard_conforming_strings
1935+
scstr = self.settings.get('standard_conforming_strings')
1936+
if scstr is None:
19491937
# warn about non-standard strings
19501938
cm = element.ClientNotice(
19511939
message = 'standard conforming strings are not available',
19521940
severity = 'WARNING',
19531941
code = '01-00',
1942+
hint = 'Backend does not support "SET standard_conforming_strings TO ON"'
19541943
)
19551944
cm = self._convert_pq_message(cm)
19561945
cm.creator = self
19571946
cm.raise_message()
1947+
elif scstr.lower() not in ('on','true','yes'):
1948+
self.settings['standard_conforming_strings'] = str(True)
19581949
super().connect()
19591950

19601951
def _pq_push(self, xact, controller = None):
@@ -1998,6 +1989,11 @@ def _raise_pq_error(self, xact = None, controller = None):
19981989
err.__cause__ = fromexc
19991990
raise err
20001991

1992+
def _raise_a_pq_error(self, em, controller):
1993+
err = self._error_lookup(em)
1994+
err.creator = controller
1995+
raise err
1996+
20011997
def _decode_pq_message(self, msg):
20021998
'decode the values in a message(E,N)'
20031999
if type(msg) in (element.ClientError, element.ClientNotice):

postgresql/protocol/typio.py

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -585,38 +585,36 @@ def raise_pack_tuple_error(procs, tup, itemnum):
585585
if len(data) > 80:
586586
# Be sure not to fill screen with noise.
587587
data = data[:75] + ' ...'
588-
te = pg_exc.ColumnError(
588+
raise pg_exc.ColumnError(
589589
"failed to pack attribute %d, %s::%s, of composite %s for transfer" %(
590590
itemnum,
591591
attnames[itemnum],
592592
typnames[itemnum],
593593
composite_name,
594594
),
595595
details = {
596-
'data': data,
596+
'context': data,
597+
'position' : str(itemnum)
597598
},
598599
)
599-
te.index = itemnum
600-
raise te
601600

602601
def raise_unpack_tuple_error(procs, tup, itemnum):
603602
data = repr(tup[itemnum])
604603
if len(data) > 80:
605604
# Be sure not to fill screen with noise.
606605
data = data[:75] + ' ...'
607-
te = pg_exc.ColumnError(
606+
raise pg_exc.ColumnError(
608607
"failed to unpack attribute %d, %s::%s, of composite %s from wire data" %(
609608
itemnum,
610609
attnames[itemnum],
611610
typnames[itemnum],
612611
composite_name,
613612
),
614613
details = {
615-
'data': data,
614+
'context': data,
615+
'position' : str(itemnum),
616616
},
617617
)
618-
te.index = itemnum
619-
raise te
620618

621619
def unpack_a_record(data):
622620
data = tuple([x[1] for x in ts.record_unpack(data)])

0 commit comments

Comments
 (0)