Skip to content

Commit 661a80f

Browse files
author
James William Pye
committed
Add transaction CM tests and relocate db-api tests into another file.
Formalize expectations about InFailedTransactionError's. driver will now rollback on exit when it sees a failed transaction. In situations where there was no exception, it will create an InFailedTransactionError and raise it after the rollback.
1 parent 4544938 commit 661a80f

4 files changed

Lines changed: 908 additions & 806 deletions

File tree

postgresql/driver/pq3.py

Lines changed: 44 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,11 @@ def from_statement(
222222
with_scroll = False,
223223
insensitive = True,
224224
):
225+
if statement._input is not None:
226+
if len(parameters) != len(statement._input):
227+
raise TypeError("statement requires %d parameters, given %d" %(
228+
len(statement._input), len(parameters)
229+
))
225230
c = super().__new__(typ)
226231
c.parameters = parameters
227232
c.statement = statement
@@ -1427,7 +1432,7 @@ def __enter__(self):
14271432
self._restore.append(res)
14281433

14291434
def __exit__(self, exc, val, tb):
1430-
# If the transaction is open, restore the settings.
1435+
# Iff the transaction is open, restore the settings.
14311436
self._restored.update(self._restore[-1])
14321437
del self._restore[-1]
14331438
if not self.database.xact.failed:
@@ -1608,14 +1613,13 @@ def _execute(self, qstring, adjustment):
16081613
pq.element.Query(self.database.typio._encode(qstring)[0]),
16091614
))
16101615
self.database._pq_push(x)
1616+
self.database._pq_complete()
16111617

1612-
# The operation is going to happen. Adjust the depth accordingly.
16131618
if adjustment < 0 and self._depth <= -adjustment:
16141619
self.__init__(self.database)
16151620
self._depth = 0
16161621
else:
16171622
self._depth += adjustment
1618-
self.database._pq_complete()
16191623

16201624
def _start_block_string(mode, isolation):
16211625
return 'START TRANSACTION' + (
@@ -1705,13 +1709,28 @@ def checkpoint(self, isolation = None, mode = None):
17051709
__enter__ = start
17061710
def __context__(self):
17071711
return self
1708-
def __exit__(self, type, value, tb):
1709-
if not self.database.closed:
1710-
if type is None:
1711-
self.commit()
1712+
1713+
def __exit__(self, typ, value, tb):
1714+
if typ is None:
1715+
# No exception, but in a failed transaction?
1716+
if self.failed is True:
1717+
err = pg_exc.InFailedTransactionError(
1718+
"invalid block exit detected",
1719+
source = 'DRIVER',
1720+
)
1721+
self.ife_descend(err)
1722+
self.rollback()
1723+
raise err
17121724
else:
1725+
# Everything is fine.
1726+
self.commit()
1727+
else:
1728+
# There's an exception, so only rollback if the connection
1729+
# exists. If the rollback() was called here, it would just
1730+
# contribute noise to the error.
1731+
if not self.database.closed:
17131732
self.rollback()
1714-
return type is None
1733+
return typ is None
17151734

17161735
def __call__(self, gid = None, mode = None, isolation = None):
17171736
if self._depth == 0:
@@ -1901,12 +1920,16 @@ def prepare(self,
19011920
ps = PreparedStatement.from_string(sql_statement_string, self)
19021921
self.ife_descend(ps)
19031922
ps._init()
1923+
if self.xact.depth > 0:
1924+
ps._fini()
19041925
return ps
19051926

19061927
def statement_from_id(self, statement_id : str) -> PreparedStatement:
19071928
ps = PreparedStatement(statement_id, self)
19081929
self.ife_descend(ps)
19091930
ps._init()
1931+
if self.xact.depth > 0:
1932+
ps._fini()
19101933
return ps
19111934

19121935
def proc(self, proc_id : (str, int)) -> StoredProcedure:
@@ -1918,6 +1941,8 @@ def cursor_from_id(self, cursor_id : str) -> Cursor:
19181941
c = Cursor(cursor_id, self)
19191942
self.ife_descend(c)
19201943
c._init()
1944+
if self.xact.depth > 0:
1945+
c._fini()
19211946
return c
19221947

19231948
def close(self):
@@ -1930,10 +1955,8 @@ def close(self):
19301955
finally:
19311956
if self.socket is not None:
19321957
self.socket.close()
1933-
self._clear()
19341958
self._reset()
1935-
# the data in the closed connection transaction is
1936-
# in utf-8.
1959+
# the data in the closed connection transaction is in utf-8.
19371960
self.typio.set_encoding('utf-8')
19381961

19391962
@property
@@ -2059,7 +2082,8 @@ def connect(self, timeout = None):
20592082
self.socket.close()
20602083
self.socket = None
20612084
self._reset()
2062-
self._clear()
2085+
self._pq_in_buffer.truncate()
2086+
20632087
connection_failures.append(
20642088
(dossl, socket_maker, e)
20652089
)
@@ -2429,17 +2453,6 @@ def _pq_pop(self, xact = None):
24292453
# state is Complete, so remove it as the working transaction
24302454
self._pq_xact = None
24312455

2432-
def _clear(self):
2433-
"""
2434-
[internal] Clear container objects of data.
2435-
"""
2436-
del self._closestatements[:]
2437-
del self._closeportals[:]
2438-
2439-
self._pq_in_buffer.truncate()
2440-
self.xact.__init__(self)
2441-
self.settings._clear_cache()
2442-
24432456
def _reset(self):
24442457
"""
24452458
[internal] Reset state and connection information attributes.
@@ -2575,11 +2588,14 @@ def __init__(self,
25752588
tnkw = {}
25762589
if self.settings:
25772590
s = dict(self.settings)
2578-
sp = s.get('search_path')
2579-
if not isinstance(sp, str):
2580-
s['search_path'] = ','.join(
2581-
pg_str.quote_ident(x) for x in sp
2582-
)
2591+
if 'search_path' in self.settings:
2592+
sp = s.get('search_path')
2593+
if sp is None:
2594+
self.settings.pop('search_path')
2595+
elif not isinstance(sp, str):
2596+
s['search_path'] = ','.join(
2597+
pg_str.quote_ident(x) for x in sp
2598+
)
25832599
tnkw.update(s)
25842600

25852601
tnkw['user'] = self.user

0 commit comments

Comments
 (0)