Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

postgresql_query: add as_single_query new option #37

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
minor_changes:
- postgresql_query - add ``as_single_query`` option to execute a script content as a single query to avoid semicolon related errors (https://github.com/ansible-collections/community.postgresql/pull/37).
33 changes: 30 additions & 3 deletions plugins/modules/postgresql_query.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@
description:
- Path to a SQL script on the target machine.
- If the script contains several queries, they must be semicolon-separated.
- To run scripts containing objects with semicolons
(for example, function and procedure definitions), use I(as_single_query=yes).
- To upload dumps or to execute other complex scripts, the preferable way
is to use the M(community.postgresql.postgresql_db) module with I(state=restore).
- Mutually exclusive with I(query).
type: path
session_role:
Expand Down Expand Up @@ -81,6 +85,18 @@
type: list
elements: str
version_added: '1.0.0'
as_single_query:
description:
- If C(yes), when reading from the I(path_to_script) file,
executes its whole content in a single query.
- When C(yes), the C(query_all_results) return value
contains only the result of the last statement.
- Whether the state is reported as changed or not
is determined by the last statement of the file.
- Used only when I(path_to_script) is specified, otherwise ignored.
type: bool
default: no
version_added: '1.1.0'
seealso:
- module: community.postgresql.postgresql_db
- name: PostgreSQL Schema reference
Expand Down Expand Up @@ -125,6 +141,8 @@
db: test_db
query: INSERT INTO test_table (id, story) VALUES (2, 'my_long_story')

# If your script contains semicolons as parts of separate objects
# like functions, procedures, and so on, use "as_single_query: yes"
- name: Run queries from SQL script using UTF-8 client encoding for session
community.postgresql.postgresql_query:
db: test_db
Expand Down Expand Up @@ -254,6 +272,8 @@
# ansible.module_utils.postgres
pass

import re

from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.postgresql.plugins.module_utils.database import (
check_input,
Expand Down Expand Up @@ -332,6 +352,7 @@ def main():
encoding=dict(type='str'),
trust_input=dict(type='bool', default=True),
search_path=dict(type='list', elements='str'),
as_single_query=dict(type='bool', default=False),
)

module = AnsibleModule(
Expand All @@ -349,6 +370,7 @@ def main():
session_role = module.params["session_role"]
trust_input = module.params["trust_input"]
search_path = module.params["search_path"]
as_single_query = module.params["as_single_query"]

if not trust_input:
# Check input for potentially dangerous elements:
Expand All @@ -371,10 +393,15 @@ def main():
try:
with open(path_to_script, 'rb') as f:
query = to_native(f.read())
if ';' in query:
query_list = [q for q in query.split(';') if q != '\n']

if not as_single_query:
if ';' in query:
query_list = [q for q in query.split(';') if q != '\n']
else:
query_list.append(query)
else:
query_list.append(query)

except Exception as e:
module.fail_json(msg="Cannot read file '%s' : %s" % (path_to_script, to_native(e)))
else:
Expand Down Expand Up @@ -425,7 +452,7 @@ def main():
query_all_results.append(query_result)

if 'SELECT' not in statusmessage:
if 'UPDATE' in statusmessage or 'INSERT' in statusmessage or 'DELETE' in statusmessage:
if re.search(re.compile(r'(UPDATE|INSERT|DELETE)'), statusmessage):
s = statusmessage.split()
if len(s) == 3:
if s[2] != '0':
Expand Down
4 changes: 4 additions & 0 deletions tests/integration/targets/postgresql_query/files/test0.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
SELECT version();

SELECT story FROM test_table
WHERE id = %s OR story = 'Данные';
10 changes: 10 additions & 0 deletions tests/integration/targets/postgresql_query/files/test1.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
CREATE FUNCTION add(integer, integer) RETURNS integer
AS 'select $1 + $2;'
LANGUAGE SQL
IMMUTABLE
RETURNS NULL ON NULL INPUT;

SELECT story FROM test_table
WHERE id = %s OR story = 'Данные';

SELECT version();
Original file line number Diff line number Diff line change
Expand Up @@ -16,34 +16,19 @@
shell: psql postgres -U "{{ pg_user }}" -t -c "INSERT INTO test_table (id, story) VALUES (1, 'first'), (2, 'second'), (3, 'third');"
ignore_errors: true

- name: postgresql_query - remove SQL script if exists
become: true
file:
path: ~{{ pg_user}}/test.sql
state: absent
ignore_errors: true

- name: postgresql_query - create an empty file to check permission
become: true
file:
path: ~{{ pg_user}}/test.sql
state: touch
- name: Copy script files
become: yes
copy:
src: '{{ item }}'
dest: '~{{ pg_user }}/{{ item }}'
owner: '{{ pg_user }}'
group: '{{ pg_user }}'
mode: '0644'
force: yes
loop:
- test0.sql
- test1.sql
register: sql_file_created
ignore_errors: true
ignore_errors: yes

- name: postgresql_query - prepare SQL script
become_user: '{{ pg_user }}'
become: true
shell: echo "{{ item }}" >> ~{{ pg_user}}/test.sql
ignore_errors: true
with_items:
- SELECT version();
- SELECT story FROM test_table
- WHERE id = %s OR story = 'Данные';
when: sql_file_created
- name: postgresql_query - analyze test_table
become_user: '{{ pg_user }}'
become: true
Expand All @@ -70,7 +55,7 @@
postgresql_query:
login_user: '{{ pg_user }}'
db: postgres
path_to_script: ~{{ pg_user }}/test.sql
path_to_script: ~{{ pg_user }}/test0.sql
positional_args:
- 1
encoding: UTF-8
Expand All @@ -81,7 +66,7 @@
- assert:
that:
- result is not changed
- result.query == "\nSELECT story FROM test_table\nWHERE id = 1 OR story = 'Данные'"
- result.query == "\n\nSELECT story FROM test_table\n WHERE id = 1 OR story = 'Данные'"
- result.query_result[0].story == 'first'
- result.query_all_results[0][0].version is search('PostgreSQL')
- result.query_all_results[1][0].story == 'first'
Expand Down Expand Up @@ -532,3 +517,26 @@
- assert:
that:
- result is failed

# Tests for the as_single_query option
- name: Run queries from SQL script as a single query
become_user: '{{ pg_user }}'
become: true
postgresql_query:
login_user: '{{ pg_user }}'
db: postgres
path_to_script: ~{{ pg_user }}/test1.sql
positional_args:
- 1
encoding: UTF-8
as_single_query: yes
register: result

- name: >
Must pass. Not changed because we can only
check statusmessage of the last query
assert:
that:
- result is not changed
- result.statusmessage == 'SELECT 1' or result.statusmessage == 'SELECT'
- result.query_list[0] == "CREATE FUNCTION add(integer, integer) RETURNS integer\n AS 'select $1 + $2;'\n LANGUAGE SQL\n IMMUTABLE\n RETURNS NULL ON NULL INPUT;\n\nSELECT story FROM test_table\n WHERE id = %s OR story = 'Данные';\n\nSELECT version();\n"