async def test_proto_executescript_02(self): # Test ReadyForCommand.transaction_state await self.con.connect() await self.con.send( protocol.ExecuteScript( headers=[], script='START TRANSACTION; SELECT 1/0' ) ) await self.con.recv_match( protocol.ErrorResponse, message='division by zero' ) await self.con.recv_match( protocol.ReadyForCommand, transaction_state=protocol.TransactionState.IN_FAILED_TRANSACTION, ) # Test that the protocol is still in a failed transaction await self.con.send( protocol.ExecuteScript( headers=[], script='SELECT 1/0' ) ) await self.con.recv_match( protocol.ErrorResponse, message='current transaction is aborted' ) await self.con.recv_match( protocol.ReadyForCommand, transaction_state=protocol.TransactionState.IN_FAILED_TRANSACTION, ) # Test recovery await self.con.send( protocol.ExecuteScript( headers=[], script='ROLLBACK' ) ) await self.con.recv_match( protocol.CommandComplete, status='ROLLBACK' ) await self.con.recv_match( protocol.ReadyForCommand, transaction_state=protocol.TransactionState.NOT_IN_TRANSACTION, )
async def test_proto_gh3170_connection_lost_error(self): async with tb.start_edgedb_server( security=srv_args.ServerSecurityMode.InsecureDevMode, ) as sd: self.assertNotIn( 'edgedb_server_background_errors_total' '{source="release_pgcon"}', sd.fetch_metrics(), ) con = await sd.connect_test_protocol() try: await con.send( protocol.ExecuteScript( headers=[], script='START TRANSACTION' ) ) await con.recv_match( protocol.CommandComplete, status='START TRANSACTION' ) await con.recv_match( protocol.ReadyForCommand, transaction_state=protocol.TransactionState.IN_TRANSACTION, ) await con.aclose() self.assertNotIn( 'edgedb_server_background_errors_total' '{source="release_pgcon"}', sd.fetch_metrics(), ) except Exception: await con.aclose() raise
async def _test_connection(self, con): await con.send(protocol.ExecuteScript(headers=[], script='SELECT 1')) await con.recv_match(protocol.CommandComplete, status='SELECT') await con.recv_match( protocol.ReadyForCommand, transaction_state=protocol.TransactionState.NOT_IN_TRANSACTION, )
async def test_proto_flush_01(self): await self.con.connect() await self.con.send( protocol.Prepare( headers=[], io_format=protocol.IOFormat.BINARY, expected_cardinality=compiler.Cardinality.AT_MOST_ONE, statement_name=b'', command='SEL ECT 1', ) ) # Should come through even without an explicit 'flush' await self.con.recv_match( protocol.ErrorResponse, message="Unexpected 'SEL'" ) # Recover the protocol state from the error self.assertEqual( await self.con.sync(), protocol.TransactionState.NOT_IN_TRANSACTION) # This Prepare should be handled alright await self.con.send( protocol.Prepare( headers=[], io_format=protocol.IOFormat.BINARY, expected_cardinality=compiler.Cardinality.AT_MOST_ONE, statement_name=b'', command='SELECT 1', ), protocol.Flush() ) await self.con.recv_match( protocol.PrepareComplete, cardinality=compiler.Cardinality.AT_MOST_ONE, ) # Test that Flush has completed successfully -- the # command should be executed and no exception should # be received. # While at it, rogue ROLLBACK should be allowed. await self.con.send( protocol.ExecuteScript( headers=[], script='ROLLBACK' ) ) await self.con.recv_match( protocol.CommandComplete, status='ROLLBACK' ) await self.con.recv_match( protocol.ReadyForCommand, transaction_state=protocol.TransactionState.NOT_IN_TRANSACTION, )
async def test_proto_connection_lost_cancel_query(self): async with self._fixture() as (con1, con2, conn_args): # Use an implicit transaction in the nested connection: lock # the row with an UPDATE, and then hold the transaction for 10 # seconds, which is long enough for the upcoming cancellation await con1.send( protocol.ExecuteScript( headers=[], script="""\ UPDATE tclcq SET { p := 'inner' }; SELECT sys::_sleep(10); """, ) ) # Take up all free backend connections other_conns = [] for _ in range(2): con = await tconn.async_connect_test_client(**conn_args) other_conns.append(con) self.loop.create_task( con.execute("SELECT sys::_sleep(60)") ).add_done_callback(lambda f: f.exception()) await asyncio.sleep(0.1) try: # Close the nested connection without waiting for the result; # the server is supposed to cancel the pending query. self.loop.call_later(0.5, self.loop.create_task, con1.aclose()) # In the outer connection, let's wait until the lock is # released by either an expected cancellation, or an unexpected # commit after 10 seconds. tx = con2.transaction() await asyncio.wait_for(tx.start(), 2) try: await con2.execute("UPDATE tclcq SET { p := 'lock' }") except edgedb.TransactionSerializationError: # In case the nested transaction succeeded, we'll meet an # concurrent update error here, which can be safely ignored pass finally: await tx.rollback() # Let's check what's in the row - if the cancellation didn't # happen, the test will fail with value "inner". val = await con2.query_single('SELECT tclcq.p LIMIT 1') self.assertEqual(val, 'initial') finally: for con in other_conns: con.terminate() for con in other_conns: await con.aclose()
async def test_proto_executescript_01(self): # Test that ExecuteScript returns ErrorResponse immediately. await self.con.connect() await self.con.send( protocol.ExecuteScript( headers=[], script='SELECT 1/0' ) ) await self.con.recv_match( protocol.ErrorResponse, message='division by zero' ) await self.con.recv_match( protocol.ReadyForCommand, transaction_state=protocol.TransactionState.NOT_IN_TRANSACTION, ) # Test that the protocol has recovered. await self.con.send( protocol.ExecuteScript( headers=[], script='SELECT 1' ) ) await self.con.recv_match( protocol.CommandComplete, status='SELECT' ) await self.con.recv_match( protocol.ReadyForCommand, transaction_state=protocol.TransactionState.NOT_IN_TRANSACTION, )