Skip to content

Commit 0db70a9

Browse files
Fix query exception handling (microsoft#244)
* added new abstract driver property query_canceled_error to access psycopg2.extensions.QueryCanceledError * altered query exception handling to use abstracted driver properties * fixed batch execute function to avoid "cursor does not exist" operational error
1 parent 1c36c01 commit 0db70a9

File tree

5 files changed

+48
-30
lines changed

5 files changed

+48
-30
lines changed

pgsqltoolsservice/driver/types/driver.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,13 @@ def database_error(self) -> str:
7373
@property
7474
@abstractmethod
7575
def transaction_in_error(self) -> bool:
76+
"""Returns bool indicating if transaction is in error"""
77+
pass
78+
79+
@property
80+
@abstractmethod
81+
def query_canceled_error(self) -> Exception:
82+
"""Returns driver query canceled error"""
7683
pass
7784

7885
@property

pgsqltoolsservice/driver/types/psycopg_driver.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,13 +131,19 @@ def default_database(self):
131131

132132
@property
133133
def database_error(self):
134-
""" Returns the type of database error this connection throws"""
134+
"""Returns the type of database error this connection throws"""
135135
return self._database_error
136136

137137
@property
138138
def transaction_in_error(self) -> bool:
139+
"""Returns bool indicating if transaction is in error"""
139140
return self._conn.get_transaction_status() is psycopg2.extensions.TRANSACTION_STATUS_INERROR
140141

142+
@property
143+
def query_canceled_error(self) -> Exception:
144+
"""Returns driver query canceled error"""
145+
return psycopg2.extensions.QueryCanceledError
146+
141147
@property
142148
def cancellation_query(self) -> str:
143149
backend_pid = self._conn.get_backend_pid()

pgsqltoolsservice/driver/types/pymysql_driver.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,11 @@ def database_error(self):
166166
def transaction_in_error(self) -> int:
167167
pass
168168

169+
@property
170+
def query_canceled_error(self) -> Exception:
171+
"""Returns query canceled error type"""
172+
pass
173+
169174
@property
170175
def cancellation_query(self) -> str:
171176
# TODO generate a query that kills the current query process

pgsqltoolsservice/query/batch.py

Lines changed: 27 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -118,33 +118,33 @@ def execute(self, conn: ServerConnection) -> None:
118118
if self._batch_events and self._batch_events._on_execution_started:
119119
self._batch_events._on_execution_started(self)
120120

121-
with self.get_cursor(conn) as cursor:
122-
try:
123-
cursor.execute(self.batch_text)
124-
# Commit the transaction if autocommit is True
125-
if conn.autocommit:
126-
conn.commit()
127-
128-
self.after_execute(cursor)
129-
except conn.database_error as error:
130-
self._has_error = True
131-
# We just raise the error with primary message and not the cursor stacktrace
132-
raise error
133-
finally:
134-
# We are doing this because when the execute fails for named cursors
135-
# cursor is not activated on the server which results in failure on close
136-
# Hence we are checking if the cursor was really executed for us to close it
137-
if cursor and cursor.rowcount != -1 and cursor.rowcount is not None:
138-
cursor.close()
139-
self._has_executed = True
140-
self._execution_end_time = datetime.now()
141-
142-
# TODO: PyMySQL doesn't support notices from a connection
143-
# self._notices = _get_notices(cursor.connection).notices
144-
# cursor.connection.notices = []
145-
146-
if self._batch_events and self._batch_events._on_execution_completed:
147-
self._batch_events._on_execution_completed(self)
121+
try:
122+
cursor = self.get_cursor(conn)
123+
cursor.execute(self.batch_text)
124+
# Commit the transaction if autocommit is True
125+
if conn.autocommit:
126+
conn.commit()
127+
128+
self.after_execute(cursor)
129+
except conn.database_error as error:
130+
self._has_error = True
131+
# We just raise the error with primary message and not the cursor stacktrace
132+
raise conn.database_error(error.diag.message_primary) from error
133+
finally:
134+
# We are doing this because when the execute fails for named cursors
135+
# cursor is not activated on the server which results in failure on close
136+
# Hence we are checking if the cursor was really executed for us to close it
137+
if cursor and cursor.rowcount != -1 and cursor.rowcount is not None:
138+
cursor.close()
139+
self._has_executed = True
140+
self._execution_end_time = datetime.now()
141+
142+
# TODO: PyMySQL doesn't support notices from a connection
143+
# self._notices = _get_notices(cursor.connection).notices
144+
# cursor.connection.notices = []
145+
146+
if self._batch_events and self._batch_events._on_execution_completed:
147+
self._batch_events._on_execution_completed(self)
148148

149149
def after_execute(self, cursor) -> None:
150150
if cursor.description is not None:

pgsqltoolsservice/query_execution/query_execution_service.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -417,7 +417,7 @@ def _get_query_text_from_execute_params(self, params: ExecuteRequestParamsBase):
417417

418418
def _resolve_query_exception(self, e: Exception, query: Query, worker_args: ExecuteRequestWorkerArgs, is_rollback_error=False):
419419
utils.log.log_debug(self._service_provider.logger, f'Query execution failed for following query: {query.query_text}\n {e}')
420-
if isinstance(e, conn.database_error) or isinstance(e, RuntimeError):
420+
if isinstance(e, worker_args.connection.database_error) or isinstance(e, RuntimeError) or isinstance(e, worker_args.connection.query_canceled_error):
421421
error_message = str(e)
422422
else:
423423
error_message = 'Unhandled exception while executing query: {}'.format(str(e)) # TODO: Localize
@@ -434,7 +434,7 @@ def _resolve_query_exception(self, e: Exception, query: Query, worker_args: Exec
434434

435435
# If there was a failure in the middle of a transaction, roll it back.
436436
# Note that conn.rollback() won't work since the connection is in autocommit mode
437-
if not is_rollback_error and worker_args.connection.get_transaction_status() is psycopg2.extensions.TRANSACTION_STATUS_INERROR:
437+
if not is_rollback_error and worker_args.connection.transaction_in_error:
438438
rollback_query = Query(query.owner_uri, 'ROLLBACK', QueryExecutionSettings(ExecutionPlanOptions(), None), QueryEvents())
439439
try:
440440
rollback_query.execute(worker_args.connection)

0 commit comments

Comments
 (0)