Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 16 additions & 3 deletions asyncpg/transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,12 +184,25 @@ async def __commit(self):
query = 'COMMIT;'

try:
await self._connection.execute(query)
status = await self._connection.execute(query)
except BaseException:
self._state = TransactionState.FAILED
raise
else:
self._state = TransactionState.COMMITTED

if not self._nested and status == 'ROLLBACK':
# PostgreSQL answers COMMIT of an aborted transaction with
# a ROLLBACK, so the changes are lost and must not be reported
# as committed.
self._state = TransactionState.ROLLEDBACK
raise apg_errors.InFailedSQLTransactionError.new({
'C': '25P02',
'S': 'ERROR',
'M': 'the transaction was rolled back by the server '
'instead of being committed',
'H': 'An earlier statement in the transaction failed.',
})

self._state = TransactionState.COMMITTED

async def __rollback(self):
self.__check_state('rollback')
Expand Down
35 changes: 35 additions & 0 deletions tests/test_transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,41 @@ async def test_transaction_interface_errors(self):
async with tr:
pass

async def test_transaction_commit_rolled_back_by_server(self):
await self.con.execute('CREATE TABLE mytab (a int)')
try:
tr = self.con.transaction()
await tr.start()
await self.con.execute('INSERT INTO mytab (a) VALUES (1)')
with self.assertRaises(asyncpg.DivisionByZeroError):
await self.con.execute('SELECT 1 / 0')

with self.assertRaisesRegex(asyncpg.InFailedSQLTransactionError,
'rolled back by the server'):
await tr.commit()

self.assertTrue(repr(tr).startswith(
'<asyncpg.Transaction state:rolledback'))
self.assertIsNone(self.con._top_xact)
self.assertFalse(self.con.is_in_transaction())

with self.assertRaisesRegex(asyncpg.InterfaceError,
'cannot rollback; .* already rolled'):
await tr.rollback()

with self.assertRaises(asyncpg.InFailedSQLTransactionError):
async with self.con.transaction():
await self.con.execute('INSERT INTO mytab (a) VALUES (2)')
with self.assertRaises(asyncpg.DivisionByZeroError):
await self.con.execute('SELECT 1 / 0')

self.assertIsNone(self.con._top_xact)
self.assertFalse(self.con.is_in_transaction())
self.assertEqual(
await self.con.fetchval('SELECT count(*) FROM mytab'), 0)
finally:
await self.con.execute('DROP TABLE mytab')

async def test_transaction_within_manual_transaction(self):
self.assertIsNone(self.con._top_xact)
self.assertFalse(self.con.is_in_transaction())
Expand Down