From 07d446825a1a8baf715110191c5a684352f13f35 Mon Sep 17 00:00:00 2001 From: tcraxs Date: Fri, 7 Jun 2019 13:34:57 +0200 Subject: [PATCH] New module postgresql_sequence (#55027) * postgresql_sequence: initial commit with new module and tests * change return variables and adjust tests * fix sanity checks * fix linter errors * change formatting functions and put params alphabetically * add new examples and remove restrict option * remove restrict option * remove `required: false` * add links to documentation * change some minor parts in documentation * add new integration tests for ** drop cascade ** change owner * change usage of owner and created a test case for set owner during creation * remove some documents and use docsfragments * add aliases for minvalue and maxvalue * change to warn if sequence not exists but should be removed * use connect_to_db from module_utils/postgres.py * add checkmode for several tests * fix psycopg2 import and connect_to_db * add a test for drop nonexistent sequence * change get_info funcrtion to use only one SQL statement * rewrite the module for cleaner code * remove psycopg2 * change check_mode behavior * add docstrings for class and methods * add test for create sequence with owner in check_mode * fix typo in set_schema() * fix docu and cleanup the code * adjust documentation for state, schema and newschema * remove mutually_exclusive for 'absent' * remove unused code comments * remove warning for drop non-exicsting sequence * change autocommit condition * adjust state documentation --- lib/ansible/module_utils/database.py | 2 +- .../postgresql/postgresql_sequence.py | 621 ++++++++++++++++ .../targets/postgresql/tasks/main.yml | 4 + .../postgresql/tasks/postgresql_sequence.yml | 696 ++++++++++++++++++ 4 files changed, 1322 insertions(+), 1 deletion(-) create mode 100644 lib/ansible/modules/database/postgresql/postgresql_sequence.py create mode 100644 test/integration/targets/postgresql/tasks/postgresql_sequence.yml diff --git a/lib/ansible/module_utils/database.py b/lib/ansible/module_utils/database.py index 096094004c..e8223fddc6 100644 --- a/lib/ansible/module_utils/database.py +++ b/lib/ansible/module_utils/database.py @@ -38,7 +38,7 @@ class UnclosedQuoteError(SQLParseError): # maps a type of identifier to the maximum number of dot levels that are # allowed to specify that identifier. For example, a database column can be # specified by up to 4 levels: database.schema.table.column -_PG_IDENTIFIER_TO_DOT_LEVEL = dict(database=1, schema=2, table=3, column=4, role=1, tablespace=1) +_PG_IDENTIFIER_TO_DOT_LEVEL = dict(database=1, schema=2, table=3, column=4, role=1, tablespace=1, sequence=3) _MYSQL_IDENTIFIER_TO_DOT_LEVEL = dict(database=1, table=2, column=3, role=1, vars=1) diff --git a/lib/ansible/modules/database/postgresql/postgresql_sequence.py b/lib/ansible/modules/database/postgresql/postgresql_sequence.py new file mode 100644 index 0000000000..aefc143554 --- /dev/null +++ b/lib/ansible/modules/database/postgresql/postgresql_sequence.py @@ -0,0 +1,621 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2019, Tobias Birkefeld (@tcraxs) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +ANSIBLE_METADATA = { + 'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'community' +} + +DOCUMENTATION = r''' +--- +module: postgresql_sequence +short_description: Create, drop, or alter a PostgreSQL sequence +description: +- Allows to create, drop or change the definition of a sequence generator + U(https://www.postgresql.org/docs/current/sql-createsequence.html). +version_added: '2.9' +options: + sequence: + description: + - The name of the sequence. + required: true + type: str + aliases: + - name + state: + description: + - The sequence state. + - If I(state=absent) other options will be ignored except of I(name) and + I(schema). + default: present + choices: [ absent, present ] + type: str + data_type: + description: + - Specifies the data type of the sequence. Valid types are bigint, integer, + and smallint. bigint is the default. The data type determines the default + minimum and maximum values of the sequence. For more info see the + documention + U(https://www.postgresql.org/docs/current/sql-createsequence.html). + - Supported from PostgreSQL 10. + choices: [ bigint, integer, smallint ] + type: str + increment: + description: + - Increment specifies which value is added to the current sequence value + to create a new value. + - A positive value will make an ascending sequence, a negative one a + descending sequence. The default value is 1. + type: int + minvalue: + description: + - Minvalue determines the minimum value a sequence can generate. The + default for an ascending sequence is 1. The default for a descending + sequence is the minimum value of the data type. + type: int + aliases: + - min + maxvalue: + description: + - Maxvalue determines the maximum value for the sequence. The default for + an ascending sequence is the maximum + value of the data type. The default for a descending sequence is -1. + type: int + aliases: + - max + start: + description: + - Start allows the sequence to begin anywhere. The default starting value + is I(minvalue) for ascending sequences and I(maxvalue) for descending + ones. + type: int + cache: + description: + - Cache specifies how many sequence numbers are to be preallocated and + stored in memory for faster access. The minimum value is 1 (only one + value can be generated at a time, i.e., no cache), and this is also + the default. + type: int + cycle: + description: + - The cycle option allows the sequence to wrap around when the I(maxvalue) + or I(minvalue) has been reached by an ascending or descending sequence + respectively. If the limit is reached, the next number generated will be + the minvalue or maxvalue, respectively. + - If C(false) (NO CYCLE) is specified, any calls to nextval after the sequence + has reached its maximum value will return an error. False (NO CYCLE) is + the default. + type: bool + cascade: + description: + - Automatically drop objects that depend on the sequence, and in turn all + objects that depend on those objects. + - Ignored if I(state=present). + - Only used with I(state=absent). + type: bool + rename_to: + description: + - The new name for the I(sequence). + - Works only for existing sequences. + type: str + owner: + description: + - Set the owner for the I(sequence). + type: str + schema: + description: + - The schema of the I(sequence). This is be used to create and relocate + a I(sequence) in the given schema. + default: public + type: str + newschema: + description: + - The new schema for the I(sequence). Will be used for moving a + I(sequence) to another I(schema). + - Works only for existing sequences. + type: str + session_role: + description: + - Switch to session_role after connecting. The specified I(session_role) + must be a role that the current I(login_user) is a member of. + - Permissions checking for SQL commands is carried out as though + the I(session_role) were the one that had logged in originally. + type: str + db: + description: + - Name of database to connect to and run queries against. + type: str + aliases: + - database + - login_db +notes: +- If you do not pass db parameter, sequence will be created in the database + named postgres. +author: +- Tobias Birkefeld (@tcraxs) +extends_documentation_fragment: postgres +''' + +EXAMPLES = r''' +- name: Create an ascending bigint sequence called foobar in the default + database + postgresql_sequence: + name: foobar + +- name: Create an ascending integer sequence called foobar, starting at 101 + postgresql_sequence: + name: foobar + data_type: integer + start: 101 + +- name: Create an descending sequence called foobar, starting at 101 and + preallocated 10 sequence numbers in cache + postgresql_sequence: + name: foobar + increment: -1 + cache: 10 + start: 101 + +- name: Create an ascending sequence called foobar, which cycle between 1 to 10 + postgresql_sequence: + name: foobar + cycle: yes + min: 1 + max: 10 + +- name: Create an ascending bigint sequence called foobar in the default + database with owner foobar + postgresql_sequence: + name: foobar + owner: foobar + +- name: Rename an existing sequence named foo to bar + postgresql_sequence: + name: foo + rename_to: bar + +- name: Change the schema of an existing sequence to foobar + postgresql_sequence: + name: foobar + newschema: foobar + +- name: Change the owner of an existing sequence to foobar + postgresql_sequence: + name: foobar + owner: foobar + +- name: Drop a sequence called foobar + postgresql_sequence: + name: foobar + state: absent + +- name: Drop a sequence called foobar with cascade + postgresql_sequence: + name: foobar + cascade: yes + state: absent +''' + +RETURN = r''' +state: + description: Sequence state at the end of execution. + returned: always + type: str + sample: 'present' +sequence: + description: Sequence name. + returned: always + type: str + sample: 'foobar' +queries: + description: List of queries that was tried to be executed. + returned: always + type: str + sample: [ "CREATE SEQUENCE \"foo\"" ] +schema: + description: Name of the schema of the sequence + returned: always + type: str + sample: 'foo' +data_type: + description: Shows the current data type of the sequence. + returned: always + type: str + sample: 'bigint' +increment: + description: The value of increment of the sequence. A positive value will + make an ascending sequence, a negative one a descending + sequence. + returned: always + type: int + sample: '-1' +minvalue: + description: The value of minvalue of the sequence. + returned: always + type: int + sample: '1' +maxvalue: + description: The value of maxvalue of the sequence. + returned: always + type: int + sample: '9223372036854775807' +start: + description: The value of start of the sequence. + returned: always + type: int + sample: '12' +cycle: + description: Shows if the sequence cycle or not. + returned: always + type: str + sample: 'NO' +owner: + description: Shows the current owner of the sequence + after the successful run of the task. + returned: always + type: str + sample: 'postgres' +newname: + description: Shows the new sequence name after rename. + returned: on success + type: str + sample: 'barfoo' +newschema: + description: Shows the new schema of the sequence after schema change. + returned: on success + type: str + sample: 'foobar' +''' + + +try: + from psycopg2.extras import DictCursor +except ImportError: + # psycopg2 is checked by connect_to_db() + # from ansible.module_utils.postgres + pass + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.database import pg_quote_identifier +from ansible.module_utils.postgres import connect_to_db, postgres_common_argument_spec +from ansible.module_utils._text import to_native + + +def exec_sql(obj, query, ddl=False, add_to_executed=True): + """Execute SQL. + + Auxiliary function for PostgreSQL user classes. + + Returns a query result if possible or True/False if ddl=True arg was passed. + It necessary for statements that don't return any result (like DDL queries). + + Arguments: + obj (obj) -- must be an object of a user class. + The object must have module (AnsibleModule class object) and + cursor (psycopg cursor object) attributes + query (str) -- SQL query to execute + ddl (bool) -- must return True or False instead of rows (typical for DDL queries) + (default False) + add_to_executed (bool) -- append the query to obj.executed_queries attribute + """ + try: + obj.cursor.execute(query) + + if add_to_executed: + obj.executed_queries.append(query) + + if not ddl: + res = obj.cursor.fetchall() + return res + return True + except Exception as e: + obj.module.fail_json(msg="Cannot execute SQL '%s': %s" % (query, to_native(e))) + return False + + +class Sequence(object): + """Implements behavior of CREATE, ALTER or DROP SEQUENCE PostgreSQL command. + + Arguments: + module (AnsibleModule) -- object of AnsibleModule class + cursor (cursor) -- cursor objec of psycopg2 library + + Attributes: + module (AnsibleModule) -- object of AnsibleModule class + cursor (cursor) -- cursor objec of psycopg2 library + changed (bool) -- something was changed after execution or not + executed_queries (list) -- executed queries + name (str) -- name of the sequence + owner (str) -- name of the owner of the sequence + schema (str) -- name of the schema (default: public) + data_type (str) -- data type of the sequence + start_value (int) -- value of the sequence start + minvalue (int) -- minimum value of the sequence + maxvalue (int) -- maximum value of the sequence + increment (int) -- increment value of the sequence + cycle (bool) -- sequence can cycle or not + new_name (str) -- name of the renamed sequence + new_schema (str) -- name of the new schema + exists (bool) -- sequence exists or not + """ + + def __init__(self, module, cursor): + self.module = module + self.cursor = cursor + self.executed_queries = [] + self.name = self.module.params['sequence'] + self.owner = '' + self.schema = self.module.params['schema'] + self.data_type = '' + self.start_value = '' + self.minvalue = '' + self.maxvalue = '' + self.increment = '' + self.cycle = '' + self.new_name = '' + self.new_schema = '' + self.exists = False + # Collect info + self.get_info() + + def get_info(self): + """Getter to refresh and get sequence info""" + query = ("SELECT " + "s.sequence_schema AS schemaname, " + "s.sequence_name AS sequencename, " + "pg_get_userbyid(c.relowner) AS sequenceowner, " + "s.data_type::regtype AS data_type, " + "s.start_value AS start_value, " + "s.minimum_value AS min_value, " + "s.maximum_value AS max_value, " + "s.increment AS increment_by, " + "s.cycle_option AS cycle " + "FROM information_schema.sequences s " + "JOIN pg_class c ON c.relname = s.sequence_name " + "LEFT JOIN pg_namespace n ON n.oid = c.relnamespace " + "WHERE NOT pg_is_other_temp_schema(n.oid) " + "AND c.relkind = 'S'::\"char\" " + "AND sequence_name = '%s' " + "AND sequence_schema = '%s'" % (self.name, + self.schema)) + + res = exec_sql(self, query, add_to_executed=False) + + if not res: + self.exists = False + return False + + if res: + self.exists = True + self.schema = res[0]['schemaname'] + self.name = res[0]['sequencename'] + self.owner = res[0]['sequenceowner'] + self.data_type = res[0]['data_type'] + self.start_value = res[0]['start_value'] + self.minvalue = res[0]['min_value'] + self.maxvalue = res[0]['max_value'] + self.increment = res[0]['increment_by'] + self.cycle = res[0]['cycle'] + + def create(self): + """Implements CREATE SEQUENCE command behavior.""" + query = ['CREATE SEQUENCE'] + query.append(self.__add_schema()) + + if self.module.params.get('data_type'): + query.append('AS %s' % self.module.params['data_type']) + + if self.module.params.get('increment'): + query.append('INCREMENT BY %s' % self.module.params['increment']) + + if self.module.params.get('minvalue'): + query.append('MINVALUE %s' % self.module.params['minvalue']) + + if self.module.params.get('maxvalue'): + query.append('MAXVALUE %s' % self.module.params['maxvalue']) + + if self.module.params.get('start'): + query.append('START WITH %s' % self.module.params['start']) + + if self.module.params.get('cache'): + query.append('CACHE %s' % self.module.params['cache']) + + if self.module.params.get('cycle'): + query.append('CYCLE') + + return exec_sql(self, ' '.join(query), ddl=True) + + def drop(self): + """Implements DROP SEQUENCE command behavior.""" + query = ['DROP SEQUENCE'] + query.append(self.__add_schema()) + + if self.module.params.get('cascade'): + query.append('CASCADE') + + return exec_sql(self, ' '.join(query), ddl=True) + + def rename(self): + """Implements ALTER SEQUENCE RENAME TO command behavior.""" + query = ['ALTER SEQUENCE'] + query.append(self.__add_schema()) + query.append('RENAME TO %s' % pg_quote_identifier(self.module.params['rename_to'], 'sequence')) + + return exec_sql(self, ' '.join(query), ddl=True) + + def set_owner(self): + """Implements ALTER SEQUENCE OWNER TO command behavior.""" + query = ['ALTER SEQUENCE'] + query.append(self.__add_schema()) + query.append('OWNER TO %s' % pg_quote_identifier(self.module.params['owner'], 'role')) + + return exec_sql(self, ' '.join(query), ddl=True) + + def set_schema(self): + """Implements ALTER SEQUENCE SET SCHEMA command behavior.""" + query = ['ALTER SEQUENCE'] + query.append(self.__add_schema()) + query.append('SET SCHEMA %s' % pg_quote_identifier(self.module.params['newschema'], 'schema')) + + return exec_sql(self, ' '.join(query), ddl=True) + + def __add_schema(self): + return '.'.join([pg_quote_identifier(self.schema, 'schema'), + pg_quote_identifier(self.name, 'sequence')]) + + +# =========================================== +# Module execution. +# + +def main(): + argument_spec = postgres_common_argument_spec() + argument_spec.update( + sequence=dict(type='str', required=True, aliases=['name']), + state=dict(type='str', default='present', choices=['absent', 'present']), + data_type=dict(type='str', choices=['bigint', 'integer', 'smallint']), + increment=dict(type='int'), + minvalue=dict(type='int', aliases=['min']), + maxvalue=dict(type='int', aliases=['max']), + start=dict(type='int'), + cache=dict(type='int'), + cycle=dict(type='bool'), + schema=dict(type='str', default='public'), + cascade=dict(type='bool'), + rename_to=dict(type='str'), + owner=dict(type='str'), + newschema=dict(type='str'), + db=dict(type='str', default='', aliases=['login_db', 'database']), + session_role=dict(type='str'), + ) + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + mutually_exclusive=[ + ['rename_to', 'data_type'], + ['rename_to', 'increment'], + ['rename_to', 'minvalue'], + ['rename_to', 'maxvalue'], + ['rename_to', 'start'], + ['rename_to', 'cache'], + ['rename_to', 'cycle'], + ['rename_to', 'cascade'], + ['rename_to', 'owner'], + ['rename_to', 'newschema'], + ['cascade', 'data_type'], + ['cascade', 'increment'], + ['cascade', 'minvalue'], + ['cascade', 'maxvalue'], + ['cascade', 'start'], + ['cascade', 'cache'], + ['cascade', 'cycle'], + ['cascade', 'owner'], + ['cascade', 'newschema'], + ] + ) + + # Note: we don't need to check mutually exclusive params here, because they are + # checked automatically by AnsibleModule (mutually_exclusive=[] list above). + + # Change autocommit to False if check_mode: + autocommit = not module.check_mode + # Connect to DB and make cursor object: + db_connection = connect_to_db(module, autocommit=autocommit) + cursor = db_connection.cursor(cursor_factory=DictCursor) + + ############## + # Create the object and do main job: + data = Sequence(module, cursor) + + # Set defaults: + changed = False + + # Create new sequence + if not data.exists and module.params['state'] == 'present': + if module.params.get('rename_to'): + module.fail_json(msg="Sequence '%s' does not exist, nothing to rename" % module.params['sequence']) + if module.params.get('newschema'): + module.fail_json(msg="Sequence '%s' does not exist, change of schema not possible" % module.params['sequence']) + + changed = data.create() + + # Drop non-existing sequence + elif not data.exists and module.params['state'] == 'absent': + # Nothing to do + changed = False + + # Drop existing sequence + elif data.exists and module.params['state'] == 'absent': + changed = data.drop() + + # Rename sequence + if data.exists and module.params.get('rename_to'): + if data.name != module.params['rename_to']: + changed = data.rename() + if changed: + data.new_name = module.params['rename_to'] + + # Refresh information + if module.params['state'] == 'present': + data.get_info() + + # Change owner, schema and settings + if module.params['state'] == 'present' and data.exists: + # change owner + if module.params.get('owner'): + if data.owner != module.params['owner']: + changed = data.set_owner() + + # Set schema + if module.params.get('newschema'): + if data.schema != module.params['newschema']: + changed = data.set_schema() + if changed: + data.new_schema = module.params['newschema'] + + # Rollback if it's possible and check_mode: + if module.check_mode: + db_connection.rollback() + else: + db_connection.commit() + + cursor.close() + db_connection.close() + + # Make return values: + kw = dict( + changed=changed, + state='present', + sequence=data.name, + queries=data.executed_queries, + schema=data.schema, + data_type=data.data_type, + increment=data.increment, + minvalue=data.minvalue, + maxvalue=data.maxvalue, + start=data.start_value, + cycle=data.cycle, + owner=data.owner, + ) + + if module.params['state'] == 'present': + if data.new_name: + kw['newname'] = data.new_name + if data.new_schema: + kw['newschema'] = data.new_schema + + elif module.params['state'] == 'absent': + kw['state'] = 'absent' + + module.exit_json(**kw) + + +if __name__ == '__main__': + main() diff --git a/test/integration/targets/postgresql/tasks/main.yml b/test/integration/targets/postgresql/tasks/main.yml index 5d3a21b61e..c760b83428 100644 --- a/test/integration/targets/postgresql/tasks/main.yml +++ b/test/integration/targets/postgresql/tasks/main.yml @@ -837,6 +837,10 @@ - include: postgresql_copy.yml when: postgres_version_resp.stdout is version('9.4', '>=') +# Test postgresql_sequence module +- include: postgresql_sequence.yml + when: postgres_version_resp.stdout is version('9.0', '>=') + # Test postgresql_ext. # pg_extension system view is available from PG 9.1. # The tests are restricted by Fedora because there will be errors related with diff --git a/test/integration/targets/postgresql/tasks/postgresql_sequence.yml b/test/integration/targets/postgresql/tasks/postgresql_sequence.yml new file mode 100644 index 0000000000..0c580bff06 --- /dev/null +++ b/test/integration/targets/postgresql/tasks/postgresql_sequence.yml @@ -0,0 +1,696 @@ +# Copyright: (c) 2019, Tobias Birkefeld (@tcraxs) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +# Preparation for tests. +- name: postgresql_sequence - create DB + become_user: "{{ pg_user }}" + become: yes + postgresql_db: + state: present + name: "{{ db_name }}" + owner: "{{ db_user1 }}" + login_user: "{{ pg_user }}" + +- name: postgresql_sequence - create a user to be owner of a database + become_user: "{{ pg_user }}" + become: yes + postgresql_user: + name: "{{ db_user1 }}" + state: present + encrypted: yes + password: password + role_attr_flags: LOGIN + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + +- name: Create a user to be owner of a sequence + become_user: "{{ pg_user }}" + become: yes + postgresql_user: + name: "{{ db_user2 }}" + state: present + encrypted: yes + password: password + role_attr_flags: LOGIN + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + +- name: postgresql_sequence - create a schema + become_user: "{{ pg_user }}" + become: yes + postgresql_schema: + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + name: foobar_schema + +#################### +# Test: create sequence in checkmode +- name: postgresql_sequence - create a new seqeunce with name "foobar" in check_mode + become_user: "{{ pg_user }}" + become: yes + check_mode: yes + postgresql_sequence: + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + name: foobar + register: result + +# Checks +- name: postgresql_sequence - check with assert the output + assert: + that: + - result.changed == True + - result.sequence == 'foobar' + - result.queries == ["CREATE SEQUENCE \"public\".\"foobar\""] + +# Real SQL check +- name: postgresql_sequence - check that the new seqeunce "foobar" not exists + become: yes + become_user: "{{ pg_user }}" + postgresql_query: + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + query: "SELECT sequence_name FROM information_schema.sequences WHERE sequence_name = 'foobar'" + register: result + +- name: postgresql_sequence - check with assert the output + assert: + that: + - result.rowcount == 0 + - result.statusmessage == 'SELECT 0' + +#################### +# Test: create sequence +- name: postgresql_sequence - create a new seqeunce with name "foobar" + become_user: "{{ pg_user }}" + become: yes + postgresql_sequence: + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + name: foobar + register: result + +# Checks +- name: postgresql_sequence - check with assert the output + assert: + that: + - result.changed == True + - result.sequence == 'foobar' + - result.queries == ["CREATE SEQUENCE \"public\".\"foobar\""] + +# Real SQL check +- name: postgresql_sequence - check that the new seqeunce "foobar" exists + become_user: "{{ pg_user }}" + become: yes + postgresql_query: + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + query: "SELECT sequence_name FROM information_schema.sequences WHERE sequence_name = 'foobar'" + register: result + +- name: postgresql_sequence - check with assert the output + assert: + that: + - result.rowcount == 1 + +#################### +# Test: drop sequence in checkmode +- name: postgresql_sequence - drop a sequence called foobar + become_user: "{{ pg_user }}" + become: yes + check_mode: yes + postgresql_sequence: + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + name: foobar + state: absent + register: result + +# Checks +- name: postgresql_sequence - check with assert the output + assert: + that: + - result.changed == True + - result.sequence == 'foobar' + - result.queries == ["DROP SEQUENCE \"public\".\"foobar\""] + +# Real SQL check +- name: postgresql_sequence - check that the seqeunce "foobar" still exists + become_user: "{{ pg_user }}" + become: yes + postgresql_query: + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + query: "SELECT sequence_name FROM information_schema.sequences WHERE sequence_name = 'foobar'" + register: result + +- name: postgresql_sequence - check with assert the output + assert: + that: + - result.rowcount == 1 + +#################### +# Test: drop sequence +- name: postgresql_sequence - drop a sequence called foobar + become_user: "{{ pg_user }}" + become: yes + postgresql_sequence: + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + name: foobar + state: absent + register: result + +# Checks +- name: postgresql_sequence - check with assert the output + assert: + that: + - result.changed == True + - result.sequence == 'foobar' + - result.queries == ["DROP SEQUENCE \"public\".\"foobar\""] + +# Real SQL check +- name: postgresql_sequence - check that the seqeunce "foobar" not exists + become_user: "{{ pg_user }}" + become: yes + postgresql_query: + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + query: "SELECT sequence_name FROM information_schema.sequences WHERE sequence_name = 'foobar'" + register: result + +- name: postgresql_sequence - check with assert the output + assert: + that: + - result.rowcount == 0 + +#################### +# Test: drop nonexistent sequence +- name: postgresql_sequence - drop a sequence called foobar which does not exists + become_user: "{{ pg_user }}" + become: yes + postgresql_sequence: + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + name: foobar + state: absent + register: result + +# Checks +- name: postgresql_sequence - check with assert the output + assert: + that: + - result.changed == False + - result.sequence == 'foobar' + - result.queries == [] + +# Real SQL check +- name: postgresql_sequence - check that the seqeunce "foobar" not exists + become_user: "{{ pg_user }}" + become: yes + postgresql_query: + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + query: "SELECT sequence_name FROM information_schema.sequences WHERE sequence_name = 'foobar'" + register: result + +- name: postgresql_sequence - check with assert the output + assert: + that: + - result.rowcount == 0 + +#################### +# Test: create sequence with options +- name: postgresql_sequence - create an descending sequence called foobar_desc, starting at 101 and which cycle between 1 to 1000 + become_user: "{{ pg_user }}" + become: yes + postgresql_sequence: + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + name: foobar_desc + increment: -1 + start: 101 + minvalue: 1 + maxvalue: 1000 + cycle: yes + register: result + +# Checks +- name: postgresql_sequence - check with assert the output + assert: + that: + - result.changed == True + - result.sequence == 'foobar_desc' + - result.increment == '-1' + - result.minvalue == '1' + - result.maxvalue == '1000' + - result.cycle == 'YES' + - result.queries == ["CREATE SEQUENCE \"public\".\"foobar_desc\" INCREMENT BY -1 MINVALUE 1 MAXVALUE 1000 START WITH 101 CYCLE"] + +# Real SQL check +- name: postgresql_sequence - check that the new seqeunce "foobar_desc" exists + postgresql_query: + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + query: "SELECT sequence_name FROM information_schema.sequences WHERE sequence_name = 'foobar_desc'" + register: result + +- name: postgresql_sequence - check with assert the output + assert: + that: + - result.rowcount == 1 + +#################### +# Test: rename a sequence in checkmode +- name: postgresql_sequence - rename an existing sequence named foobar_desc to foobar_with_options + become_user: "{{ pg_user }}" + become: yes + check_mode: yes + postgresql_sequence: + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + name: foobar_desc + rename_to: foobar_with_options + register: result + +# Checks +- name: postgresql_sequence - check with assert the output + assert: + that: + - result.changed == True + - result.sequence == 'foobar_desc' + - result.newname == 'foobar_with_options' + - result.queries == ["ALTER SEQUENCE \"public\".\"foobar_desc\" RENAME TO \"foobar_with_options\""] + +# Real SQL check +- name: postgresql_sequence - check that the seqeunce "foobar_desc" still exists and is not renamed + become_user: "{{ pg_user }}" + become: yes + postgresql_query: + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + query: "SELECT sequence_name FROM information_schema.sequences WHERE sequence_name = 'foobar_desc'" + register: result + +- name: postgresql_sequence - check with assert the output + assert: + that: + - result.rowcount == 1 + +#################### +# Test: rename a sequence +- name: postgresql_sequence - rename an existing sequence named foobar_desc to foobar_with_options + become_user: "{{ pg_user }}" + become: yes + postgresql_sequence: + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + name: foobar_desc + rename_to: foobar_with_options + register: result + +# Checks +- name: postgresql_sequence - check with assert the output + assert: + that: + - result.changed == True + - result.sequence == 'foobar_desc' + - result.newname == 'foobar_with_options' + - result.queries == ["ALTER SEQUENCE \"public\".\"foobar_desc\" RENAME TO \"foobar_with_options\""] + +# Real SQL check +- name: postgresql_sequence - check that the renamed seqeunce "foobar_with_options" exists + become_user: "{{ pg_user }}" + become: yes + postgresql_query: + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + query: "SELECT sequence_name FROM information_schema.sequences WHERE sequence_name = 'foobar_with_options'" + register: result + +- name: postgresql_sequence - check with assert the output + assert: + that: + - result.rowcount == 1 + +#################### +# Test: change schema of a sequence in checkmode +- name: postgresql_sequence - change schema of an existing sequence from public to foobar_schema + become_user: "{{ pg_user }}" + become: yes + check_mode: yes + postgresql_sequence: + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + name: foobar_with_options + newschema: foobar_schema + register: result + +# Checks +- name: postgresql_sequence - check with assert the output + assert: + that: + - result.changed == True + - result.sequence == 'foobar_with_options' + - result.schema == 'public' + - result.newschema == 'foobar_schema' + - result.queries == ["ALTER SEQUENCE \"public\".\"foobar_with_options\" SET SCHEMA \"foobar_schema\""] + +# Real SQL check +- name: postgresql_sequence - check that the seqeunce "foobar_with_options" still exists in the old schema + become_user: "{{ pg_user }}" + become: yes + postgresql_query: + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + query: "SELECT sequence_name,sequence_schema FROM information_schema.sequences WHERE sequence_name = 'foobar_with_options' AND sequence_schema = 'public'" + register: result + +- name: postgresql_sequence - check with assert the output + assert: + that: + - result.rowcount == 1 + +#################### +# Test: change schema of a sequence +- name: postgresql_sequence - change schema of an existing sequence from public to foobar_schema + become_user: "{{ pg_user }}" + become: yes + postgresql_sequence: + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + name: foobar_with_options + newschema: foobar_schema + register: result + +# Checks +- name: postgresql_sequence - check with assert the output + assert: + that: + - result.changed == True + - result.sequence == 'foobar_with_options' + - result.schema == 'public' + - result.newschema == 'foobar_schema' + - result.queries == ["ALTER SEQUENCE \"public\".\"foobar_with_options\" SET SCHEMA \"foobar_schema\""] + +# Real SQL check +- name: postgresql_sequence - check that the seqeunce "foobar_with_options" exists in new schema + become_user: "{{ pg_user }}" + become: yes + postgresql_query: + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + query: "SELECT sequence_name,sequence_schema FROM information_schema.sequences WHERE sequence_name = 'foobar_with_options' AND sequence_schema = 'foobar_schema'" + register: result + +- name: postgresql_sequence - check with assert the output + assert: + that: + - result.rowcount == 1 + +#################### +# Test: change owner of a sequence in checkmode +- name: postgresql_sequence - change owner of an existing sequence from "{{ pg_user }}" to "{{ db_user1 }}" + become_user: "{{ pg_user }}" + become: yes + check_mode: yes + postgresql_sequence: + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + name: foobar_with_options + schema: foobar_schema + owner: "{{ db_user1 }}" + register: result + +# Checks +- name: postgresql_sequence - check with assert the output + assert: + that: + - result.changed == True + - result.sequence == 'foobar_with_options' + - result.owner == "{{ pg_user }}" + - result.queries == ["ALTER SEQUENCE \"foobar_schema\".\"foobar_with_options\" OWNER TO \"{{ db_user1 }}\""] + +# Real SQL check +- name: postgresql_sequence - check that the seqeunce "foobar_with_options" has still the old owner + become_user: "{{ pg_user }}" + become: yes + postgresql_query: + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + query: "SELECT c.relname,a.rolname,n.nspname + FROM pg_class as c + JOIN pg_authid as a on (c.relowner = a.oid) + JOIN pg_namespace as n on (c.relnamespace = n.oid) + WHERE c.relkind = 'S' and + c.relname = 'foobar_with_options' and + n.nspname = 'foobar_schema' and + a.rolname = '{{ pg_user }}'" + register: result + +- name: postgresql_sequence - check with assert the output + assert: + that: + - result.rowcount == 1 + +#################### +# Test: change owner of a sequence +- name: postgresql_sequence - change owner of an existing sequence from "{{ pg_user }}" to "{{ db_user1 }}" + become_user: "{{ pg_user }}" + become: yes + postgresql_sequence: + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + name: foobar_with_options + schema: foobar_schema + owner: "{{ db_user1 }}" + register: result + +# Checks +- name: postgresql_sequence - check with assert the output + assert: + that: + - result.changed == True + - result.sequence == 'foobar_with_options' + - result.owner == "{{ pg_user }}" + - result.queries == ["ALTER SEQUENCE \"foobar_schema\".\"foobar_with_options\" OWNER TO \"{{ db_user1 }}\""] + +# Real SQL check +- name: postgresql_sequence - check that the seqeunce "foobar_with_options" has a new owner + become_user: "{{ pg_user }}" + become: yes + postgresql_query: + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + query: "SELECT c.relname,a.rolname,n.nspname + FROM pg_class as c + JOIN pg_authid as a on (c.relowner = a.oid) + JOIN pg_namespace as n on (c.relnamespace = n.oid) + WHERE c.relkind = 'S' and + c.relname = 'foobar_with_options' and + n.nspname = 'foobar_schema' and + a.rolname = '{{ db_user1 }}'" + register: result + +- name: postgresql_sequence - check with assert the output + assert: + that: + - result.rowcount == 1 + +#################### +# Test: drop seqeunce with cascade + +# CREATE SEQUENCE seq1; +# CREATE TABLE t1 (f1 INT NOT NULL DEFAULT nextval('seq1')); +# DROP SEQUENCE seq1 CASCADE; +- name: postgresql_sequence - create sequence for drop cascade test + become_user: "{{ pg_user }}" + become: yes + postgresql_sequence: + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + name: seq1 + +- name: postgresql_sequence - create table which use seqeunce for drop cascade test + become_user: "{{ pg_user }}" + become: yes + postgresql_table: + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + name: t1 + columns: + - f1 INT NOT NULL DEFAULT nextval('seq1') + +#################### +# Test: drop seqeunce with cascade in checkmode +- name: postgresql_sequence - drop with cascade a sequence called seq1 + become_user: "{{ pg_user }}" + become: yes + check_mode: yes + postgresql_sequence: + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + name: seq1 + state: absent + cascade: yes + register: result + +# Checks +- name: postgresql_sequence - check with assert the output + assert: + that: + - result.changed == True + - result.sequence == 'seq1' + - result.queries == ["DROP SEQUENCE \"public\".\"seq1\" CASCADE"] + +# Real SQL check +- name: postgresql_sequence - check that the seqeunce "seq1" still exists + become_user: "{{ pg_user }}" + become: yes + postgresql_query: + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + query: "SELECT sequence_name FROM information_schema.sequences WHERE sequence_name = 'seq1'" + register: result + +- name: postgresql_sequence - check with assert the output + assert: + that: + - result.rowcount == 1 + +#################### +# Test: drop seqeunce with cascade +- name: postgresql_sequence - drop with cascade a sequence called seq1 + become_user: "{{ pg_user }}" + become: yes + postgresql_sequence: + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + name: seq1 + state: absent + cascade: yes + register: result + +# Checks +- name: postgresql_sequence - check with assert the output + assert: + that: + - result.changed == True + - result.sequence == 'seq1' + - result.queries == ["DROP SEQUENCE \"public\".\"seq1\" CASCADE"] + +# Real SQL check +- name: postgresql_sequence - check that the seqeunce "seq1" not exists + become_user: "{{ pg_user }}" + become: yes + postgresql_query: + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + query: "SELECT sequence_name FROM information_schema.sequences WHERE sequence_name = 'seq1'" + register: result + +- name: postgresql_sequence - check with assert the output + assert: + that: + - result.rowcount == 0 + +#################### +# Test: create sequence with owner in checkmode +- name: postgresql_sequence - create a new seqeunce with name "foobar2" with owner "{{ db_user2 }}" + become_user: "{{ pg_user }}" + become: yes + check_mode: yes + postgresql_sequence: + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + name: foobar2 + owner: "{{ db_user2 }}" + register: result + +# Checks +- name: postgresql_sequence - check with assert the output + assert: + that: + - result.changed == True + - result.sequence == 'foobar2' + - result.queries == ["CREATE SEQUENCE \"public\".\"foobar2\"", "ALTER SEQUENCE \"public\".\"foobar2\" OWNER TO \"ansible_db_user2\""] + +# Real SQL check +- name: postgresql_sequence - check that the new seqeunce "foobar2" does not exists + become_user: "{{ pg_user }}" + become: yes + postgresql_query: + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + query: "SELECT sequence_name FROM information_schema.sequences WHERE sequence_name = 'foobar2'" + register: result + +- name: postgresql_sequence - check with assert the output + assert: + that: + - result.rowcount == 0 + +#################### +# Test: create sequence with owner +- name: postgresql_sequence - create a new seqeunce with name "foobar2" with owner "{{ db_user2 }}" + become_user: "{{ pg_user }}" + become: yes + postgresql_sequence: + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + name: foobar2 + owner: "{{ db_user2 }}" + register: result + +# Checks +- name: postgresql_sequence - check with assert the output + assert: + that: + - result.changed == True + - result.sequence == 'foobar2' + - result.queries == ["CREATE SEQUENCE \"public\".\"foobar2\"", "ALTER SEQUENCE \"public\".\"foobar2\" OWNER TO \"ansible_db_user2\""] + +# Real SQL check +- name: postgresql_sequence - check that the new seqeunce "foobar2" exists + become_user: "{{ pg_user }}" + become: yes + postgresql_query: + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + query: "SELECT sequence_name FROM information_schema.sequences WHERE sequence_name = 'foobar2'" + register: result + +- name: postgresql_sequence - check with assert the output + assert: + that: + - result.rowcount == 1 + +- name: postgresql_sequence - check that the seqeunce "foobar2" has owner "{{ db_user2 }}" + become_user: "{{ pg_user }}" + become: yes + postgresql_query: + db: "{{ db_name }}" + login_user: "{{ pg_user }}" + query: "SELECT c.relname,a.rolname,n.nspname + FROM pg_class as c + JOIN pg_authid as a on (c.relowner = a.oid) + JOIN pg_namespace as n on (c.relnamespace = n.oid) + WHERE c.relkind = 'S' and + c.relname = 'foobar2' and + n.nspname = 'public' and + a.rolname = '{{ db_user2 }}'" + register: result + +- name: postgresql_sequence - check with assert the output + assert: + that: + - result.rowcount == 1 + +# Cleanup +- name: postgresql_sequence - destroy DB + become_user: "{{ pg_user }}" + become: yes + postgresql_db: + state: absent + name: "{{ db_name }}" + login_user: "{{ pg_user }}"