mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
mssql_script: allow non-returning SQL statements (#6457)
* feat: Allow non-returning SQL statements - The current implementation fails out when certain statements or batches do not have resultsets - this limits the usefulness of the module - Instead, it is known that statements without resultsets return then OperationalError exception with text "Statement not executed or executed statement has no resultset". We will utilize these facts to accept these statements - The implementation also assumes that users will always use best- practices for the script syntax; that is, "GO" will always be capitalized but this is not strictly required -- update to allow "GO" to be any mixed-case Signed-off-by: Lesley Kimmel <lesley.j.kimmel@gmail.com> * feat: Add changelog fragment for change - Add changelog fragment for PR 6192 Signed-off-by: Lesley Kimmel <lesley.j.kimmel@gmail.com> * feat: Improve batching - Previous batching had shortcomings like making strict assumptions about the format of the incoming script and did not handle Windows- based scripts (e.g. \r characters). It also did not handle cases where there were trailing or leading whitespace characters round the 'GO' - Added a special case for removing the Byte Order Mark (BOM) character that may come as part of a script when slurped from some hosts. Signed-off-by: Lesley Kimmel <lesley.j.kimmel@gmail.com> * feat: Use str.splitlines() - Use of this method is cleaner Signed-off-by: Lesley Kimmel <lesley.j.kimmel@gmail.com> * Update changelogs/fragments/6192-allow-empty-resultsets.yml Co-authored-by: Felix Fontein <felix@fontein.de> * fix: Update transcribing errors - Replace local namespace with project namespace - Remove 'return' statement from the module.fail_json call Signed-off-by: Lesley Kimmel <lesley.j.kimmel@gmail.com> --------- Signed-off-by: Lesley Kimmel <lesley.j.kimmel@gmail.com> Co-authored-by: Lesley Kimmel <lesleyk@vmware.com> Co-authored-by: Felix Fontein <felix@fontein.de>
This commit is contained in:
parent
61a0dc4370
commit
165182cdbf
3 changed files with 73 additions and 5 deletions
4
changelogs/fragments/6192-allow-empty-resultsets.yml
Normal file
4
changelogs/fragments/6192-allow-empty-resultsets.yml
Normal file
|
@ -0,0 +1,4 @@
|
|||
minor_changes:
|
||||
- mssql_script - handle error condition for empty resultsets to allow for non-returning SQL statements (for example ``UPDATE`` and ``INSERT``) (https://github.com/ansible-collections/community.general/pull/6457).
|
||||
- mssql_script - allow for ``GO`` statement to be mixed-case for scripts not using strict syntax (https://github.com/ansible-collections/community.general/pull/6457).
|
||||
- mssql_script - improve batching logic to allow a wider variety of input scripts. For example, SQL scripts slurped from Windows machines which may contain carriage return (''\r'') characters (https://github.com/ansible-collections/community.general/pull/6457).
|
|
@ -280,14 +280,32 @@ def run_module():
|
|||
cursor = conn.cursor(as_dict=True)
|
||||
query_results_key = 'query_results_dict'
|
||||
|
||||
queries = script.split('\nGO\n')
|
||||
# Process the script into batches
|
||||
queries = []
|
||||
current_batch = []
|
||||
for statement in script.splitlines(keepends=True):
|
||||
# Ignore the Byte Order Mark, if found
|
||||
if statement.strip() == '\uFEFF':
|
||||
continue
|
||||
|
||||
# Assume each 'GO' is on its own line but may have leading/trailing whitespace
|
||||
# and be of mixed-case
|
||||
if statement.strip().upper() != 'GO':
|
||||
current_batch.append(statement)
|
||||
else:
|
||||
queries.append(''.join(current_batch))
|
||||
current_batch = []
|
||||
if len(current_batch) > 0:
|
||||
queries.append(''.join(current_batch))
|
||||
|
||||
result['changed'] = True
|
||||
if module.check_mode:
|
||||
module.exit_json(**result)
|
||||
|
||||
query_results = []
|
||||
try:
|
||||
for query in queries:
|
||||
# Catch and exit on any bad query errors
|
||||
try:
|
||||
cursor.execute(query, sql_params)
|
||||
qry_result = []
|
||||
rows = cursor.fetchall()
|
||||
|
@ -296,7 +314,16 @@ def run_module():
|
|||
rows = cursor.fetchall()
|
||||
query_results.append(qry_result)
|
||||
except Exception as e:
|
||||
return module.fail_json(msg="query failed", query=query, error=str(e), **result)
|
||||
# We know we executed the statement so this error just means we have no resultset
|
||||
# which is ok (eg UPDATE/INSERT)
|
||||
if (
|
||||
type(e).__name__ == 'OperationalError' and
|
||||
str(e) == 'Statement not executed or executed statement has no resultset'
|
||||
):
|
||||
query_results.append([])
|
||||
else:
|
||||
error_msg = '%s: %s' % (type(e).__name__, str(e))
|
||||
module.fail_json(msg="query failed", query=query, error=error_msg, **result)
|
||||
|
||||
# ensure that the result is json serializable
|
||||
qry_results = json.loads(json.dumps(query_results, default=clean_output))
|
||||
|
|
|
@ -49,6 +49,19 @@
|
|||
login_port: "{{ mssql_port }}"
|
||||
script: "SELECT 1"
|
||||
|
||||
- name: Execute a malformed query
|
||||
community.general.mssql_script:
|
||||
login_user: "{{ mssql_login_user }}"
|
||||
login_password: "{{ mssql_login_password }}"
|
||||
login_host: "{{ mssql_host }}"
|
||||
login_port: "{{ mssql_port }}"
|
||||
script: "SELCT 1"
|
||||
failed_when: false
|
||||
register: bad_query
|
||||
- assert:
|
||||
that:
|
||||
- bad_query.error.startswith('ProgrammingError')
|
||||
|
||||
- name: two batches with default output
|
||||
community.general.mssql_script:
|
||||
login_user: "{{ mssql_login_user }}"
|
||||
|
@ -135,6 +148,30 @@
|
|||
- result_batches_dict.query_results_dict[0][0] | length == 1 # one row in first select
|
||||
- result_batches_dict.query_results_dict[0][0][0]['b0s0'] == 'Batch 0 - Select 0' # column 'b0s0' of first row
|
||||
|
||||
- name: Multiple batches with no resultsets and mixed-case GO
|
||||
community.general.mssql_script:
|
||||
login_user: "{{ mssql_login_user }}"
|
||||
login_password: "{{ mssql_login_password }}"
|
||||
login_host: "{{ mssql_host }}"
|
||||
login_port: "{{ mssql_port }}"
|
||||
script: |
|
||||
CREATE TABLE #integration56yH2 (c1 VARCHAR(10), c2 VARCHAR(10))
|
||||
Go
|
||||
INSERT INTO #integration56yH2 VALUES ('C1_VALUE1', 'C2_VALUE1')
|
||||
gO
|
||||
UPDATE #integration56yH2 SET c2 = 'C2_VALUE2' WHERE c1 = 'C1_VALUE1'
|
||||
go
|
||||
SELECT * from #integration56yH2
|
||||
GO
|
||||
DROP TABLE #integration56yH2
|
||||
register: empty_batches
|
||||
- assert:
|
||||
that:
|
||||
- empty_batches.query_results | length == 5 # five batch results
|
||||
- empty_batches.query_results[3][0] | length == 1 # one row in select
|
||||
- empty_batches.query_results[3][0][0] | length == 2 # two columns in row
|
||||
- empty_batches.query_results[3][0][0][1] == 'C2_VALUE2' # value has been updated
|
||||
|
||||
- name: Stored procedure may return multiple result sets
|
||||
community.general.mssql_script:
|
||||
login_user: "{{ mssql_login_user }}"
|
||||
|
|
Loading…
Reference in a new issue