Skip to content

Commit a851632

Browse files
authored
Fix for malformed query leading to CLI hanging. (dbcli#140)
* Fixing queryexecution service to handle error during query execution. * Fixing format of for statement. * Fixing pep8 space after comma. * Adding 2 second sleep after connection. * Adding regression test. * Updating baseline. * Fixing baseline.
1 parent da65e2c commit a851632

File tree

6 files changed

+70
-26
lines changed

6 files changed

+70
-26
lines changed

mssqlcli/jsonrpc/contracts/queryexecutestringservice.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ def get_response(self):
3030
if response:
3131
decoded_response = self.decode_response(response)
3232

33-
if isinstance(decoded_response, QueryCompleteEvent):
33+
if isinstance(decoded_response, QueryCompleteEvent) or \
34+
isinstance(decoded_response, QueryExecuteErrorResponseEvent):
3435
self.finished = True
3536
self.json_rpc_client.request_finished(self.id)
3637

@@ -77,7 +78,7 @@ def format(self):
7778
class QueryExecuteErrorResponseEvent(object):
7879
def __init__(self, parameters):
7980
inner_params = parameters[u'error']
80-
self.error_message = inner_params[u'message'] if u'message' in inner_params else ''
81+
self.exception_message = inner_params[u'message'] if u'message' in inner_params else ''
8182
self.error_code = inner_params[u'code'] if u'code' in inner_params else ''
8283

8384

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Content-Length: 40
2+
3+
{"jsonrpc":"2.0","id":"1","result":true}Content-Length: 645
4+
5+
{"jsonrpc":"2.0","method":"connection/complete","params":{"ownerUri":"connectionservicetest","connectionId":"6fd410b5-cd48-487a-bc9c-90569e749772","messages":null,"errorMessage":null,"errorNumber":0,"serverInfo":{"serverMajorVersion":12,"serverMinorVersion":0,"serverReleaseVersion":2269,"engineEditionId":3,"serverVersion":"12.0.2269.0","serverLevel":"RTM","serverEdition":"Enterprise Edition (64-bit)","isCloud":false,"azureVersion":0,"osVersion":"Windows NT 6.3 <X64> (Build 9600: ) (Hypervisor)\n","machineName":"BRO-HB"},"connectionSummary":{"serverName":"bro-hb","databaseName":"AdventureWorks2014","userName":"cloudsa"},"type":"Default"}}Content-Length: 205
6+
7+
{"jsonrpc":"2.0","id":"2","error":{"code":0,"message":"SQL Execution error: A fatal error occurred.\r\n\tIncorrect syntax was encountered while select * from [HumanResources.Department was being parsed."}}

mssqlcli/jsonrpc/contracts/tests/generatequerybaseline.py

+2
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ def generate_query_baseline(file_name):
3434
writer = json_rpc_client.JsonRpcWriter(tools_service_process.stdin)
3535
writer.send_request(u'connection/connect', parameters, id=1)
3636

37+
time.sleep(2)
38+
3739
parameters = {u'OwnerUri': u'connectionservicetest',
3840
u'Query': "select * from HumanResources.Department"}
3941
writer.send_request(u'query/executeString', parameters, id=2)

mssqlcli/jsonrpc/contracts/tests/test_executestring.py

+50-12
Original file line numberDiff line numberDiff line change
@@ -32,16 +32,50 @@ def test_query_execute_response_AdventureWorks2014(self):
3232

3333
request = queryservice.QueryExecuteStringRequest(
3434
2, rpc_client, parameters)
35-
self.verify_query_response(request=request)
35+
self.verify_query_response(request=request,
36+
expected_message_event=1,
37+
expected_complete_event=1,
38+
expected_batch_summaries=1,
39+
expected_result_set_summaries=1,
40+
expected_row_count=16)
3641
rpc_client.shutdown()
3742

38-
def verify_query_response(self, request):
43+
def test_malformed_query_AdventureWorks2014(self):
44+
"""
45+
Verify a failed query execute response for "select * from [HumanResources.Department"
46+
"""
47+
48+
with open(self.get_test_baseline(u'malformed_query_adventureworks2014.txt'), u'r+b', buffering=0) as response_file:
49+
request_stream = io.BytesIO()
50+
rpc_client = json_rpc_client.JsonRpcClient(
51+
request_stream, response_file)
52+
rpc_client.start()
53+
# Submit a dummy request.
54+
parameters = {u'OwnerUri': u'connectionservicetest',
55+
u'Query': "select * from [HumanResources.Department"}
56+
57+
request = queryservice.QueryExecuteStringRequest(
58+
2, rpc_client, parameters)
59+
self.verify_query_response(request=request,
60+
expected_complete_event=1,
61+
expected_error_count=1)
62+
rpc_client.shutdown()
63+
64+
def verify_query_response(self,
65+
request,
66+
expected_message_event=0,
67+
expected_complete_event=0,
68+
expected_batch_summaries=0,
69+
expected_result_set_summaries=0,
70+
expected_row_count=0,
71+
expected_error_count=0):
3972
message_event = 0
4073
complete_event = 0
4174
batch_summaries = 0
4275
result_set_summaries = 0
43-
# For the executed query expected row count is 16
4476
row_count = 0
77+
error_count = 0
78+
4579
request.execute()
4680
time.sleep(1)
4781

@@ -50,18 +84,22 @@ def verify_query_response(self, request):
5084
if response:
5185
if isinstance(response, queryservice.QueryMessageEvent):
5286
message_event += 1
87+
elif isinstance(response, queryservice.QueryExecuteErrorResponseEvent):
88+
error_count += 1
5389
elif isinstance(response, queryservice.QueryCompleteEvent):
5490
complete_event += 1
55-
batch_summaries = len(response.batch_summaries)
56-
result_set_summaries = len(
57-
response.batch_summaries[0].result_set_summaries)
58-
row_count = response.batch_summaries[0].result_set_summaries[0].row_count
91+
if hasattr(response, 'batch_summaries'):
92+
batch_summaries = len(response.batch_summaries)
93+
result_set_summaries = len(
94+
response.batch_summaries[0].result_set_summaries)
95+
row_count = response.batch_summaries[0].result_set_summaries[0].row_count
5996

60-
self.assertEqual(message_event, 1)
61-
self.assertEqual(complete_event, 1)
62-
self.assertEqual(batch_summaries, 1)
63-
self.assertEqual(result_set_summaries, 1)
64-
self.assertEqual(row_count, 16)
97+
self.assertEqual(message_event, expected_message_event)
98+
self.assertEqual(complete_event, expected_complete_event)
99+
self.assertEqual(batch_summaries, expected_batch_summaries)
100+
self.assertEqual(result_set_summaries, expected_result_set_summaries)
101+
self.assertEqual(row_count, expected_row_count)
102+
self.assertEqual(error_count, expected_error_count)
65103

66104
def get_test_baseline(self, file_name):
67105
"""

mssqlcli/main.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,7 @@ def connect(self, database='', server='', user='', port='', passwd='',
312312
connection_timeout=connection_timeout,
313313
application_intent=application_intent,
314314
multi_subnet_failover=multi_subnet_failover,
315-
packet_size=packet_size,**kwargs)
315+
packet_size=packet_size, **kwargs)
316316

317317
if not self.mssqlcliclient_query_execution.connect():
318318
click.secho(
@@ -524,8 +524,8 @@ def _evaluate_command(self, text):
524524
click.secho(u'No connection to server. Exiting.')
525525
exit(1)
526526

527-
for rows, columns, status, sql, is_error in self.mssqlcliclient_query_execution.execute_multi_statement_single_batch(
528-
text):
527+
for rows, columns, status, sql, is_error in \
528+
self.mssqlcliclient_query_execution.execute_multi_statement_single_batch(text):
529529
total = time() - start
530530
if self._should_show_limit_prompt(status, rows):
531531
click.secho('The result set has more than %s rows.'
@@ -569,7 +569,7 @@ def _evaluate_command(self, text):
569569
meta_changed = meta_changed or has_meta_cmd(text)
570570

571571
return output, MetaQuery(
572-
sql, all_success, total, meta_changed, db_changed, path_changed, mutated)
572+
text, all_success, total, meta_changed, db_changed, path_changed, mutated)
573573

574574
def _handle_server_closed_connection(self):
575575
"""Used during CLI execution"""

mssqlcli/mssqlcliclient.py

+4-8
Original file line numberDiff line numberDiff line change
@@ -132,8 +132,8 @@ def execute_multi_statement_single_batch(self, query):
132132
if not sql:
133133
yield None, None, None, sql, False
134134
continue
135-
for rows, columns, status, sql, is_error in self.execute_single_batch_query(sql):
136-
yield rows, columns, status, sql, is_error
135+
for rows, columns, status, statement, is_error in self.execute_single_batch_query(sql):
136+
yield rows, columns, status, statement, is_error
137137

138138
def execute_single_batch_query(self, query):
139139
if not self.is_connected:
@@ -150,18 +150,14 @@ def execute_single_batch_query(self, query):
150150
while not query_request.completed():
151151
query_response = query_request.get_response()
152152
if query_response:
153-
if isinstance(query_response, queryservice.QueryExecuteErrorResponseEvent):
154-
yield self.tabular_results_generator(column_info=None, result_rows=None,
155-
query=query, message=query_response.error_message,
156-
is_error=True)
157-
elif isinstance(query_response, queryservice.QueryMessageEvent):
153+
if isinstance(query_response, queryservice.QueryMessageEvent):
158154
query_messages.append(query_response)
159155
else:
160156
sleep(time_wait_if_no_response)
161157

162158
if query_response.exception_message:
163159
logger.error(u'Query response had an exception')
164-
yield self.tabular_results_generator(
160+
return self.tabular_results_generator(
165161
column_info=None,
166162
result_rows=None,
167163
query=query,

0 commit comments

Comments
 (0)