1
0
Fork 0
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:
ljkimmel 2023-05-07 14:58:38 -05:00 committed by GitHub
parent 61a0dc4370
commit 165182cdbf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 73 additions and 5 deletions

View 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).

View file

@ -280,14 +280,32 @@ def run_module():
cursor = conn.cursor(as_dict=True) cursor = conn.cursor(as_dict=True)
query_results_key = 'query_results_dict' 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 result['changed'] = True
if module.check_mode: if module.check_mode:
module.exit_json(**result) module.exit_json(**result)
query_results = [] query_results = []
try: for query in queries:
for query in queries: # Catch and exit on any bad query errors
try:
cursor.execute(query, sql_params) cursor.execute(query, sql_params)
qry_result = [] qry_result = []
rows = cursor.fetchall() rows = cursor.fetchall()
@ -295,8 +313,17 @@ def run_module():
qry_result.append(rows) qry_result.append(rows)
rows = cursor.fetchall() rows = cursor.fetchall()
query_results.append(qry_result) query_results.append(qry_result)
except Exception as e: 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 # ensure that the result is json serializable
qry_results = json.loads(json.dumps(query_results, default=clean_output)) qry_results = json.loads(json.dumps(query_results, default=clean_output))

View file

@ -49,6 +49,19 @@
login_port: "{{ mssql_port }}" login_port: "{{ mssql_port }}"
script: "SELECT 1" 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 - name: two batches with default output
community.general.mssql_script: community.general.mssql_script:
login_user: "{{ mssql_login_user }}" 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] | 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 - 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 - name: Stored procedure may return multiple result sets
community.general.mssql_script: community.general.mssql_script:
login_user: "{{ mssql_login_user }}" login_user: "{{ mssql_login_user }}"