Repository: ansible-collections/community.general

Issue:
Module mssql_script using insert command returning failed on playbook but it's actually insert the records
### Summary

I've tried using the insert command and it's actually adding the record successfully but the playbook return failed, I've search for the python module pymssql which this module is using this python library, found that in python we can add conn.commit() and the error is gone but how can I do this in yaml playbook?



### Issue Type

Bug Report

### Component Name

mssql_script

### Ansible Version

```console (paste below)
$ ansible 2.9.6

```


### Community.general Version

```console (paste below)
$ ansible-galaxy collection list community.general.mssql_script

```


### Configuration

```console (paste below)
$ ansible-config dump --only-changed

```
ansible 2.9.6
  config file = /etc/ansible/ansible.cfg
  configured module search path = ['/root/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python3/dist-packages/ansible
  executable location = /usr/bin/ansible
  python version = 3.8.10 (default, Nov 14 2022, 12:59:47) [GCC 9.4.0]

### OS / Environment

Ubuntu 20.04

### Steps to Reproduce

<!--- Paste example playbooks or commands between quotes below -->
```yaml (paste below)

```
- name: test command
  hosts: win
  gather_facts: false
  tasks:
    - name: DB command
      community.general.mssql_script:
        login_user: master-user
        login_password: *******
        login_host: "{{ inventory_hostname }}"
        login_port: 1433
        db: my_own_db
        #script: "select * from my_own_db.dbo.[my-table-1]"
        script: |
          INSERT INTO my_own_db.dbo.[my-table-1] (name, message) VALUES (%(name)s, %(message)s)
        params:
          name: myname2
          message: test message2
      register: output
      delegate_to: localhost

    - debug:
        msg:
        - "{{ output.query_results }}"

### Expected Results

Should return with green color instead of red

### Actual Results

```console (paste below)

```
`The full traceback is:
  File "/tmp/ansible_community.general.mssql_script_payload_1__m0256/ansible_community.general.mssql_script_payload.zip/ansible_collections/community/general/plugins/modules/mssql_script.py", line 282, in run_module
  File "src/pymssql/_pymssql.pyx", line 554, in pymssql._pymssql.Cursor.fetchall
fatal: [13.214.77.48 -> localhost]: FAILED! => {
    "changed": true,
    "error": "Statement not executed or executed statement has no resultset",
    "invocation": {
        "module_args": {
            "db": "nook_db",
            "login_host": "my-ip_address",
            "login_password": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER",
            "login_port": 1433,
            "login_user": "master-user",
            "name": "my_own_db",
            "output": "default",
            "params": {
                "message": "test message2",
                "name": "myname2"
            },
            "script": "INSERT INTO my_own_db.dbo.[my-table-1] (name, message) VALUES (%(name)s, %(message)s)\n"
        }
    },
    "msg": "query failed",
    "query": "INSERT INTO my_own_db.dbo.[my-table-1] (name, message) VALUES (%(name)s, %(message)s)\n`

### Code of Conduct

- [X] I agree to follow the Ansible Code of Conduct


Discussion:
Files identified in the description:
* [`plugins/modules/mssql_script.py`](https://github.com/ansible-collections/community.general/blob/main/plugins/modules/mssql_script.py)

If these files are incorrect, please update the `component name` section of the description or use the `!component` bot command.

[click here for bot help](https://github.com/ansible/ansibullbot/blob/devel/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->
cc @kbudde
[click here for bot help](https://github.com/ansible/ansibullbot/blob/devel/ISSUE_HELP.md)
<!--- boilerplate: notify --->
I don't think calling `conn.commit()` should be necessary since the module sets `conn.autocommit(True)`. The error seems to indicate that calling `cursor.fetchall()` after an `INSERT` doesn't work, so probably the module needs to look closer at the command it executes to determine what to do. But then, I don't use MSSQL or this module, so :shrug:
@felixfontein  Thanks for the answer, I've figured out that the INSERT query alone return nothing and the ansible mssql_script module has nothing to handle blank result from sqlserver, so I try adding one more "SELECT 1" query and it's now green on ansible output
  

Resolving patch:
diff --git a/plugins/modules/mssql_script.py b/plugins/modules/mssql_script.py
--- a/plugins/modules/mssql_script.py
+++ b/plugins/modules/mssql_script.py
@@ -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:
+    for query in queries:
+        # Catch and exit on any bad query errors
+        try:
             cursor.execute(query, sql_params)
             qry_result = []
             rows = cursor.fetchall()
@@ -295,8 +313,17 @@ def run_module():
                 qry_result.append(rows)
                 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)
+        except Exception as e:
+            # 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))


Test patch:
diff --git a/tests/integration/targets/mssql_script/tasks/main.yml b/tests/integration/targets/mssql_script/tasks/main.yml
--- a/tests/integration/targets/mssql_script/tasks/main.yml
+++ b/tests/integration/targets/mssql_script/tasks/main.yml
@@ -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 }}"