diff --git a/docs/en_US/compound_trigger_dialog.rst b/docs/en_US/compound_trigger_dialog.rst new file mode 100644 index 000000000..f50dbfd9b --- /dev/null +++ b/docs/en_US/compound_trigger_dialog.rst @@ -0,0 +1,75 @@ +.. _compound_trigger_dialog: + +******************************** +`Compound Trigger Dialog`:index: +******************************** + +Use the *Compound Trigger* dialog to create a compound trigger or modify an +existing compound trigger. *Compound Trigger* is supported only for EPAS server +12 and above. A compound trigger executes a specified code when certain events +occur. + +The *Compound Trigger* dialog organizes the development of a compound trigger +through the following dialog tabs: *General*, *Events*, and *Code*. The *SQL* +tab displays the SQL code generated by dialog selections. + +.. image:: images/compound_trigger_general.png + :alt: Compound Trigger dialog general tab + :align: center + +Use the fields in the *General* tab to identify the compound trigger: + +* Use the *Name* field to add a descriptive name for the compound trigger. This + must be distinct from the name of any other compound trigger for the same table. + The name will be displayed in the *pgAdmin* tree control. +* Store notes about the compound trigger in the *Comment* field. + +Click the *Events* tab to continue. + +.. image:: images/compound_trigger_events.png + :alt: Compound Trigger dialog events tab + :align: center + +Use the fields in the *Events* tab to specify how and when the compound trigger fires: + +* Select the type of event(s) that will invoke the compound trigger; to select + an event type, move the switch next to the event to the *YES* position. + The supported event types are *INSERT*, *UPDATE*, *DELETE*. +* Use the *When* field to provide a boolean condition that will invoke the + compound trigger. +* If defining a column-specific compound trigger, use the *Columns* field to + specify the columns or columns that are the target of the compound trigger. + +Click the *Code* tab to continue. + +.. image:: images/compound_trigger_code.png + :alt: Compound Trigger dialog code tab + :align: center + +Use the *Code* field to specify the code for the four timing events +*BEFORE STATEMENT*, *AFTER STATEMENT*, *BEFORE EACH ROW*, *AFTER EACH ROW* +that will be invoked when the compound trigger fires. Basic template is provided +with place holders. + +Click the *SQL* tab to continue. + +Your entries in the *Compound Trigger* dialog generate a SQL command (see an example +below). Use the *SQL* tab for review; revisit or switch tabs to make any changes +to the SQL command. + +Example +******* + +The following is an example of the sql command generated by user selections in +the *Compound Trigger* dialog: + +.. image:: images/compound_trigger_sql.png + :alt: Compound Trigger dialog sql tab + :align: center + +The example demonstrates creating a compound trigger named *test_ct*. + +* Click the *Info* button (i) to access online help. +* Click the *Save* button to save work. +* Click the *Cancel* button to exit without saving work. +* Click the *Reset* button to restore configuration parameters. \ No newline at end of file diff --git a/docs/en_US/images/compound_trigger_code.png b/docs/en_US/images/compound_trigger_code.png new file mode 100644 index 000000000..819a86e97 Binary files /dev/null and b/docs/en_US/images/compound_trigger_code.png differ diff --git a/docs/en_US/images/compound_trigger_events.png b/docs/en_US/images/compound_trigger_events.png new file mode 100644 index 000000000..6c37a9c68 Binary files /dev/null and b/docs/en_US/images/compound_trigger_events.png differ diff --git a/docs/en_US/images/compound_trigger_general.png b/docs/en_US/images/compound_trigger_general.png new file mode 100644 index 000000000..3caededab Binary files /dev/null and b/docs/en_US/images/compound_trigger_general.png differ diff --git a/docs/en_US/images/compound_trigger_sql.png b/docs/en_US/images/compound_trigger_sql.png new file mode 100644 index 000000000..fd0a692c2 Binary files /dev/null and b/docs/en_US/images/compound_trigger_sql.png differ diff --git a/docs/en_US/modifying_tables.rst b/docs/en_US/modifying_tables.rst index e71da0bdb..81663f7d9 100644 --- a/docs/en_US/modifying_tables.rst +++ b/docs/en_US/modifying_tables.rst @@ -17,6 +17,7 @@ node, and select *Create Cast...* check_dialog column_dialog + compound_trigger_dialog exclusion_constraint_dialog foreign_key_dialog index_dialog diff --git a/docs/en_US/release_notes_4_12.rst b/docs/en_US/release_notes_4_12.rst index 3963ad93d..7356b486e 100644 --- a/docs/en_US/release_notes_4_12.rst +++ b/docs/en_US/release_notes_4_12.rst @@ -9,6 +9,7 @@ This release contains a number of bug fixes and new features since the release o New features ************ +| `Issue #4144 `_ - Add support of Compound Triggers for EPAS 12+. | `Issue #4333 `_ - Add support for planner support functions in PostgreSQL 12+ functions. | `Issue #4334 `_ - Add support for generated columns in Postgres 12+. | `Issue #4540 `_ - Use the full tab space for CodeMirror instances on dialogues where appropriate. @@ -43,4 +44,5 @@ Bug fixes | `Issue #4552 `_ - Fix some errors thrown on the JS console when dragging text in the Query Tool. | `Issue #4559 `_ - Ensure triggers should be updated properly for EPAS server. | `Issue #4565 `_ - Fix the reverse engineered SQL for trigger functions with the WINDOW option selected. +| `Issue #4578 `_ - Ensure enable trigger menu should be visible when trigger is disabled. | `Issue #4581 `_ - Ensure the comment on a Primary Key constraint can be edited under the Table node. \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/compound_triggers/__init__.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/compound_triggers/__init__.py new file mode 100644 index 000000000..9f4476850 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/compound_triggers/__init__.py @@ -0,0 +1,1017 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2019, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + +""" Implements Compound Trigger Node """ + +import simplejson as json +from functools import wraps + +import pgadmin.browser.server_groups.servers.databases as database +from flask import render_template, request, jsonify +from flask_babelex import gettext +from pgadmin.browser.collection import CollectionNodeModule +from pgadmin.browser.utils import PGChildNodeView +from pgadmin.utils.ajax import make_json_response, internal_server_error, \ + make_response as ajax_response, gone +from pgadmin.utils.driver import get_driver +from config import PG_DEFAULT_DRIVER +from pgadmin.utils import IS_PY2 +# If we are in Python3 +if not IS_PY2: + unicode = str + + +class CompoundTriggerModule(CollectionNodeModule): + """ + class CompoundTriggerModule(CollectionNodeModule) + + A module class for Compound Trigger node derived from + CollectionNodeModule. + + Methods: + ------- + * __init__(*args, **kwargs) + - Method is used to initialize the Trigger and it's base module. + + * get_nodes(gid, sid, did, scid, tid) + - Method is used to generate the browser collection node. + + * node_inode() + - Method is overridden from its base class to make the node as leaf node. + + * script_load() + - Load the module script for compound trigger, when any of the server + node is initialized. + """ + + NODE_TYPE = 'compound_trigger' + COLLECTION_LABEL = gettext("Compound Triggers") + + def __init__(self, *args, **kwargs): + """ + Method is used to initialize the CompoundTriggerModule and + it's base module. + + Args: + *args: + **kwargs: + """ + super(CompoundTriggerModule, self).__init__(*args, **kwargs) + self.min_ver = self.min_ppasver = 120000 + self.max_ver = None + self.min_gpdbver = 1000000000 + self.server_type = ['ppas'] + + def BackendSupported(self, manager, **kwargs): + """ + Load this module if vid is view, we will not load it under + material view + """ + if manager.server_type == 'gpdb': + return False + if super(CompoundTriggerModule, self).BackendSupported( + manager, **kwargs): + conn = manager.connection(did=kwargs['did']) + + if 'vid' not in kwargs: + return True + + template_path = 'compound_triggers/sql/{0}/#{1}#'.format( + manager.server_type, manager.version) + SQL = render_template("/".join( + [template_path, 'backend_support.sql']), vid=kwargs['vid'] + ) + + status, res = conn.execute_scalar(SQL) + + # check if any errors + if not status: + return internal_server_error(errormsg=res) + + # Check vid is view not material view + # then true, othewise false + return res + + def get_nodes(self, gid, sid, did, scid, **kwargs): + """ + Generate the collection node + """ + assert ('tid' in kwargs or 'vid' in kwargs) + yield self.generate_browser_collection_node( + kwargs['tid'] if 'tid' in kwargs else kwargs['vid'] + ) + + @property + def script_load(self): + """ + Load the module script for server, when any of the server-group node is + initialized. + """ + return database.DatabaseModule.NODE_TYPE + + @property + def node_inode(self): + """ + Load the module node as a leaf node + """ + return False + + @property + def module_use_template_javascript(self): + """ + Returns whether Jinja2 template is used for generating the javascript + module. + """ + return False + + @property + def csssnippets(self): + """ + Returns a snippet of css to include in the page + """ + snippets = [ + render_template( + "compound_triggers/css/compound_trigger.css", + node_type=self.node_type + ) + ] + + for submodule in self.submodules: + snippets.extend(submodule.csssnippets) + + return snippets + + +blueprint = CompoundTriggerModule(__name__) + + +class CompoundTriggerView(PGChildNodeView): + """ + This class is responsible for generating routes for Compound Trigger node + + Methods: + ------- + * __init__(**kwargs) + - Method is used to initialize the CompoundTriggerView and it's + base view. + + * check_precondition() + - This function will behave as a decorator which will checks + database connection before running view, it will also attaches + manager,conn & template_path properties to self + + * list() + - This function is used to list all the Compound Trigger nodes + within that collection. + + * nodes() + - This function will used to create all the child node within that + collection, Here it will create all the Compound Trigger node. + + * node() + - This function will used to create child node within that + collection, Here it will create specific the Compound Trigger node. + + * properties(gid, sid, did, scid, tid, trid) + - This function will show the properties of the selected + Compound Trigger node + + * create(gid, sid, did, scid, tid) + - This function will create the new Compound Trigger object + + * update(gid, sid, did, scid, tid, trid) + - This function will update the data for the selected + Compound Trigger node + + * delete(self, gid, sid, scid, tid, trid): + - This function will drop the Compound Trigger object + + * enable(self, gid, sid, scid, tid, trid): + - This function will enable/disable Compound Trigger object + + * msql(gid, sid, did, scid, tid, trid) + - This function is used to return modified SQL for the selected + Compound Trigger node + + * get_sql(data, scid, tid, trid) + - This function will generate sql from model data + + * sql(gid, sid, did, scid, tid, trid): + - This function will generate sql to show it in sql pane for the + selected Compound Trigger node. + + * dependency(gid, sid, did, scid, tid, trid): + - This function will generate dependency list show it in dependency + pane for the selected Compound Trigger node. + + * dependent(gid, sid, did, scid, tid, trid): + - This function will generate dependent list to show it in dependent + pane for the selected Compound Trigger node. + + * _column_details(tid, clist):: + - This function will fetch the columns for compound trigger + + * _trigger_definition(data): + - This function will set additional compound trigger definitions in + AJAX response + """ + + node_type = blueprint.node_type + + parent_ids = [ + {'type': 'int', 'id': 'gid'}, + {'type': 'int', 'id': 'sid'}, + {'type': 'int', 'id': 'did'}, + {'type': 'int', 'id': 'scid'}, + {'type': 'int', 'id': 'tid'} + ] + ids = [ + {'type': 'int', 'id': 'trid'} + ] + + operations = dict({ + 'obj': [ + {'get': 'properties', 'delete': 'delete', 'put': 'update'}, + {'get': 'list', 'post': 'create', 'delete': 'delete'} + ], + 'delete': [{'delete': 'delete'}, {'delete': 'delete'}], + 'children': [{'get': 'children'}], + 'nodes': [{'get': 'node'}, {'get': 'nodes'}], + 'sql': [{'get': 'sql'}], + 'msql': [{'get': 'msql'}, {'get': 'msql'}], + 'stats': [{'get': 'statistics'}], + 'dependency': [{'get': 'dependencies'}], + 'dependent': [{'get': 'dependents'}], + 'enable': [{'put': 'enable_disable_trigger'}] + }) + + def check_precondition(f): + """ + This function will behave as a decorator which will checks + database connection before running view, it will also attaches + manager,conn & template_path properties to self + """ + + @wraps(f) + def wrap(*args, **kwargs): + # Here args[0] will hold self & kwargs will hold gid,sid,did + self = args[0] + self.manager = get_driver(PG_DEFAULT_DRIVER).connection_manager( + kwargs['sid'] + ) + self.conn = self.manager.connection(did=kwargs['did']) + # We need datlastsysoid to check if current compound trigger + # is system trigger + self.datlastsysoid = self.manager.db_info[ + kwargs['did'] + ]['datlastsysoid'] if self.manager.db_info is not None and \ + kwargs['did'] in self.manager.db_info else 0 + + # we will set template path for sql scripts + self.template_path = 'compound_triggers/sql/{0}/#{1}#'.format( + self.manager.server_type, self.manager.version) + # Store server type + self.server_type = self.manager.server_type + # We need parent's name eg table name and schema name + # when we create new compound trigger in update we can fetch + # it using property sql + SQL = render_template("/".join([self.template_path, + 'get_parent.sql']), + tid=kwargs['tid']) + status, rset = self.conn.execute_2darray(SQL) + if not status: + return internal_server_error(errormsg=rset) + + for row in rset['rows']: + self.schema = row['schema'] + self.table = row['table'] + + # Here we are storing compound trigger definition + # We will use it to check compound trigger type definition + self.trigger_definition = { + 'TRIGGER_TYPE_ROW': (1 << 0), + 'TRIGGER_TYPE_BEFORE': (1 << 1), + 'TRIGGER_TYPE_INSERT': (1 << 2), + 'TRIGGER_TYPE_DELETE': (1 << 3), + 'TRIGGER_TYPE_UPDATE': (1 << 4), + 'TRIGGER_TYPE_TRUNCATE': (1 << 5), + 'TRIGGER_TYPE_INSTEAD': (1 << 6) + } + + return f(*args, **kwargs) + + return wrap + + @check_precondition + def list(self, gid, sid, did, scid, tid): + """ + This function is used to list all the compound trigger nodes + within that collection. + + Args: + gid: Server group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Table ID + + Returns: + JSON of available compound trigger nodes + """ + + SQL = render_template("/".join([self.template_path, + 'properties.sql']), tid=tid) + status, res = self.conn.execute_dict(SQL) + + if not status: + return internal_server_error(errormsg=res) + return ajax_response( + response=res['rows'], + status=200 + ) + + @check_precondition + def node(self, gid, sid, did, scid, tid, trid): + """ + This function will used to create the child node within that + collection. + Here it will create specific the compound trigger node. + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Table ID + trid: Trigger ID + + Returns: + JSON of available compound trigger child nodes + """ + res = [] + SQL = render_template("/".join([self.template_path, + 'nodes.sql']), + tid=tid, + trid=trid) + status, rset = self.conn.execute_2darray(SQL) + if not status: + return internal_server_error(errormsg=rset) + + if len(rset['rows']) == 0: + return gone(gettext( + """Could not find the compound trigger in the table.""") + ) + + res = self.blueprint.generate_browser_node( + rset['rows'][0]['oid'], + tid, + rset['rows'][0]['name'], + icon="icon-compound_trigger" if + rset['rows'][0]['is_enable_trigger'] else + "icon-compound_trigger-bad" + ) + + return make_json_response( + data=res, + status=200 + ) + + @check_precondition + def nodes(self, gid, sid, did, scid, tid): + """ + This function will used to create all the child node within that + collection. + Here it will create all the compound trigger node. + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Table ID + + Returns: + JSON of available trigger child nodes + """ + res = [] + SQL = render_template("/".join([self.template_path, + 'nodes.sql']), tid=tid) + status, rset = self.conn.execute_2darray(SQL) + if not status: + return internal_server_error(errormsg=rset) + + for row in rset['rows']: + res.append( + self.blueprint.generate_browser_node( + row['oid'], + tid, + row['name'], + icon="icon-compound_trigger" if row['is_enable_trigger'] + else "icon-compound_trigger-bad" + )) + + return make_json_response( + data=res, + status=200 + ) + + def _column_details(self, tid, clist): + """ + This functional will fetch list of column for compound trigger + + Args: + tid: Table OID + clist: List of columns + + Returns: + Updated properties data with column + """ + + SQL = render_template("/".join([self.template_path, + 'get_columns.sql']), + tid=tid, clist=clist) + status, rset = self.conn.execute_2darray(SQL) + if not status: + return internal_server_error(errormsg=rset) + # 'tgattr' contains list of columns from table used in + # compound trigger + columns = [] + + for row in rset['rows']: + columns.append(row['name']) + + return columns + + def _trigger_definition(self, data): + """ + This functional will set the compound trigger definition + + Args: + data: Properties data + + Returns: + Updated properties data with compound trigger definition + """ + + # Event definition + if data['tgtype'] & self.trigger_definition['TRIGGER_TYPE_INSERT']: + data['evnt_insert'] = True + else: + data['evnt_insert'] = False + + if data['tgtype'] & self.trigger_definition['TRIGGER_TYPE_DELETE']: + data['evnt_delete'] = True + else: + data['evnt_delete'] = False + + if data['tgtype'] & self.trigger_definition['TRIGGER_TYPE_UPDATE']: + data['evnt_update'] = True + else: + data['evnt_update'] = False + + if data['tgtype'] & self.trigger_definition['TRIGGER_TYPE_TRUNCATE']: + data['evnt_truncate'] = True + else: + data['evnt_truncate'] = False + + return data + + @check_precondition + def properties(self, gid, sid, did, scid, tid, trid): + """ + This function will show the properties of the selected + compound trigger node. + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + scid: Schema ID + tid: Table ID + trid: Trigger ID + + Returns: + JSON of selected compound trigger node + """ + + SQL = render_template("/".join([self.template_path, + 'properties.sql']), + tid=tid, trid=trid, + datlastsysoid=self.datlastsysoid) + + status, res = self.conn.execute_dict(SQL) + + if not status: + return internal_server_error(errormsg=res) + + if len(res['rows']) == 0: + return gone(gettext( + """Could not find the compound trigger in the table.""")) + + # Making copy of output for future use + data = dict(res['rows'][0]) + if len(data['tgattr']) >= 1: + columns = ', '.join(data['tgattr'].split(' ')) + data['columns'] = self._column_details(tid, columns) + + data = self._trigger_definition(data) + + return ajax_response( + response=data, + status=200 + ) + + @check_precondition + def create(self, gid, sid, did, scid, tid): + """ + This function will creates new the compound trigger object + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Table ID + """ + data = request.form if request.form else json.loads( + request.data, encoding='utf-8' + ) + + for k, v in data.items(): + try: + # comments should be taken as is because if user enters a + # json comment it is parsed by loads which should not happen + if k in ('description',): + data[k] = v + else: + data[k] = json.loads(v, encoding='utf-8') + except (ValueError, TypeError, KeyError): + data[k] = v + + required_args = { + 'name': 'Name' + } + + for arg in required_args: + if arg not in data: + return make_json_response( + status=410, + success=0, + errormsg=gettext( + "Could not find the required parameter (%s)." % + required_args[arg] + ) + ) + + # Adding parent into data dict, will be using it while creating sql + data['schema'] = self.schema + data['table'] = self.table + + try: + SQL = render_template("/".join([self.template_path, + 'create.sql']), + data=data, conn=self.conn) + status, res = self.conn.execute_scalar(SQL) + if not status: + return internal_server_error(errormsg=res) + + # we need oid to to add object in tree at browser + SQL = render_template("/".join([self.template_path, + 'get_oid.sql']), + tid=tid, data=data) + status, trid = self.conn.execute_scalar(SQL) + if not status: + return internal_server_error(errormsg=tid) + + return jsonify( + node=self.blueprint.generate_browser_node( + trid, + tid, + data['name'], + icon="icon-compound_trigger" + ) + ) + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def delete(self, gid, sid, did, scid, tid, trid=None): + """ + This function will updates existing the compound trigger object + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Table ID + trid: Trigger ID + """ + if trid is None: + data = request.form if request.form else json.loads( + request.data, encoding='utf-8' + ) + else: + data = {'ids': [trid]} + + # Below will decide if it's simple drop or drop with cascade call + if self.cmd == 'delete': + # This is a cascade operation + cascade = True + else: + cascade = False + + try: + for trid in data['ids']: + # We will first fetch the compound trigger name for + # current request so that we create template for + # dropping compound trigger + SQL = render_template("/".join([self.template_path, + 'properties.sql']), + tid=tid, trid=trid, + datlastsysoid=self.datlastsysoid) + + status, res = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res) + + if not res['rows']: + return make_json_response( + success=0, + errormsg=gettext( + 'Error: Object not found.' + ), + info=gettext( + 'The specified compound trigger could not be ' + 'found.\n' + ) + ) + + data = dict(res['rows'][0]) + + SQL = render_template("/".join([self.template_path, + 'delete.sql']), + data=data, + conn=self.conn, + cascade=cascade + ) + status, res = self.conn.execute_scalar(SQL) + if not status: + return internal_server_error(errormsg=res) + + return make_json_response( + success=1, + info=gettext("Compound Trigger is dropped") + ) + + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def update(self, gid, sid, did, scid, tid, trid): + """ + This function will updates existing the compound trigger object + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Table ID + trid: Trigger ID + """ + data = request.form if request.form else json.loads( + request.data, encoding='utf-8' + ) + + try: + data['schema'] = self.schema + data['table'] = self.table + + SQL, name = self.get_sql(scid, tid, trid, data) + if not isinstance(SQL, (str, unicode)): + return SQL + SQL = SQL.strip('\n').strip(' ') + status, res = self.conn.execute_scalar(SQL) + if not status: + return internal_server_error(errormsg=res) + + # We need oid to add object in browser tree and if user + # update the compound trigger then new OID is getting generated + # so we need to return new OID of compound trigger. + SQL = render_template( + "/".join([self.template_path, 'get_oid.sql']), + tid=tid, data=data + ) + status, new_trid = self.conn.execute_scalar(SQL) + if not status: + return internal_server_error(errormsg=new_trid) + # Fetch updated properties + SQL = render_template("/".join([self.template_path, + 'properties.sql']), + tid=tid, trid=new_trid, + datlastsysoid=self.datlastsysoid) + + status, res = self.conn.execute_dict(SQL) + + if not status: + return internal_server_error(errormsg=res) + + if len(res['rows']) == 0: + return gone(gettext( + """Could not find the compound trigger in the table.""")) + + # Making copy of output for future use + data = dict(res['rows'][0]) + + return jsonify( + node=self.blueprint.generate_browser_node( + new_trid, + tid, + name, + icon="icon-%s" % self.node_type if + data['is_enable_trigger'] else + "icon-%s-bad" % self.node_type + ) + ) + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def msql(self, gid, sid, did, scid, tid, trid=None): + """ + This function will generates modified sql for compound trigger object + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Table ID + trid: Trigger ID (When working with existing compound trigger) + """ + data = dict() + for k, v in request.args.items(): + try: + # comments should be taken as is because if user enters a + # json comment it is parsed by loads which should not happen + if k in ('description',): + data[k] = v + else: + data[k] = json.loads(v, encoding='utf-8') + except ValueError: + data[k] = v + + # Adding parent into data dict, will be using it while creating sql + data['schema'] = self.schema + data['table'] = self.table + + try: + sql, name = self.get_sql(scid, tid, trid, data) + if not isinstance(sql, (str, unicode)): + return sql + sql = sql.strip('\n').strip(' ') + + if sql == '': + sql = "--modified SQL" + return make_json_response( + data=sql, + status=200 + ) + except Exception as e: + return internal_server_error(errormsg=str(e)) + + def get_sql(self, scid, tid, trid, data): + """ + This function will genrate sql from model data + """ + if trid is not None: + SQL = render_template("/".join([self.template_path, + 'properties.sql']), + tid=tid, trid=trid, + datlastsysoid=self.datlastsysoid) + + status, res = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res) + if len(res['rows']) == 0: + return gone(gettext( + """Could not find the compound trigger in the table.""") + ) + + old_data = dict(res['rows'][0]) + + # If name is not present in data then + # we will fetch it from old data, we also need schema & table name + if 'name' not in data: + data['name'] = old_data['name'] + + self.trigger_name = data['name'] + self.is_trigger_enabled = old_data['is_enable_trigger'] + + if len(old_data['tgattr']) > 1: + columns = ', '.join(old_data['tgattr'].split(' ')) + old_data['columns'] = self._column_details(tid, columns) + + old_data = self._trigger_definition(old_data) + + SQL = render_template( + "/".join([self.template_path, 'update.sql']), + data=data, o_data=old_data, conn=self.conn + ) + else: + required_args = { + 'name': 'Name' + } + + for arg in required_args: + if arg not in data: + return gettext('-- definition incomplete') + + # If the request for new object which do not have did + SQL = render_template("/".join([self.template_path, 'create.sql']), + data=data, conn=self.conn) + return SQL, data['name'] if 'name' in data else old_data['name'] + + @check_precondition + def sql(self, gid, sid, did, scid, tid, trid): + """ + This function will generates reverse engineered sql for + compound trigger object + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Table ID + trid: Trigger ID + """ + + SQL = render_template("/".join([self.template_path, + 'properties.sql']), + tid=tid, trid=trid, + datlastsysoid=self.datlastsysoid) + + status, res = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res) + if len(res['rows']) == 0: + return gone(gettext( + """Could not find the compound trigger in the table.""")) + + data = dict(res['rows'][0]) + # Adding parent into data dict, will be using it while creating sql + data['schema'] = self.schema + data['table'] = self.table + + if len(data['tgattr']) >= 1: + columns = ', '.join(data['tgattr'].split(' ')) + data['columns'] = self._column_details(tid, columns) + + data = self._trigger_definition(data) + + SQL, name = self.get_sql(scid, tid, None, data) + + sql_header = u"-- Compound Trigger: {0}\n\n-- ".format(data['name']) + + sql_header += render_template("/".join([self.template_path, + 'delete.sql']), + data=data, conn=self.conn) + + SQL = sql_header + '\n\n' + SQL.strip('\n') + + # If compound trigger is disbaled then add sql code for the same + if not data['is_enable_trigger']: + SQL += '\n\n' + SQL += render_template("/".join([self.template_path, + 'enable_disable_trigger.sql']), + data=data, conn=self.conn) + + return ajax_response(response=SQL) + + @check_precondition + def enable_disable_trigger(self, gid, sid, did, scid, tid, trid): + """ + This function will enable OR disable the current + compound trigger object + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Table ID + trid: Trigger ID + """ + + data = request.form if request.form else json.loads( + request.data, encoding='utf-8' + ) + + # Convert str 'true' to boolean type + is_enable_flag = json.loads(data['enable']) + + try: + + SQL = render_template("/".join([self.template_path, + 'properties.sql']), + tid=tid, trid=trid, + datlastsysoid=self.datlastsysoid) + + status, res = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res) + if len(res['rows']) == 0: + return gone(gettext( + """Could not find the compound trigger in the table.""") + ) + + o_data = dict(res['rows'][0]) + + # If enable is set to true means we need SQL to enable + # current compound trigger which is disabled already so we need to + # alter the 'is_enable_trigger' flag so that we can render + # correct SQL for operation + o_data['is_enable_trigger'] = is_enable_flag + + # Adding parent into data dict, will be using it while creating sql + o_data['schema'] = self.schema + o_data['table'] = self.table + + SQL = render_template("/".join([self.template_path, + 'enable_disable_trigger.sql']), + data=o_data, conn=self.conn) + status, res = self.conn.execute_scalar(SQL) + + if not status: + return internal_server_error(errormsg=res) + + return make_json_response( + success=1, + info="Compound Trigger updated", + data={ + 'id': trid, + 'tid': tid, + 'scid': scid + } + ) + except Exception as e: + return internal_server_error(errormsg=str(e)) + + @check_precondition + def dependents(self, gid, sid, did, scid, tid, trid): + """ + This function get the dependents and return ajax response + for the compound trigger node. + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Table ID + trid: Trigger ID + """ + dependents_result = self.get_dependents( + self.conn, trid + ) + + return ajax_response( + response=dependents_result, + status=200 + ) + + @check_precondition + def dependencies(self, gid, sid, did, scid, tid, trid): + """ + This function get the dependencies and return ajax response + for the compound trigger node. + + Args: + gid: Server Group ID + sid: Server ID + did: Database ID + scid: Schema ID + tid: Table ID + trid: Trigger ID + + """ + dependencies_result = self.get_dependencies( + self.conn, trid + ) + + return ajax_response( + response=dependencies_result, + status=200 + ) + + +CompoundTriggerView.register_node_view(blueprint) diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/compound_triggers/static/img/coll-compound_trigger.svg b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/compound_triggers/static/img/coll-compound_trigger.svg new file mode 100644 index 000000000..24a290208 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/compound_triggers/static/img/coll-compound_trigger.svg @@ -0,0 +1 @@ +coll_compound_trigger \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/compound_triggers/static/img/compound_trigger-bad.svg b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/compound_triggers/static/img/compound_trigger-bad.svg new file mode 100644 index 000000000..ec0f8f455 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/compound_triggers/static/img/compound_trigger-bad.svg @@ -0,0 +1 @@ +compud_trigger_disable \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/compound_triggers/static/img/compound_trigger.svg b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/compound_triggers/static/img/compound_trigger.svg new file mode 100644 index 000000000..837cfe010 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/compound_triggers/static/img/compound_trigger.svg @@ -0,0 +1 @@ +compund_trigger \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/compound_triggers/static/js/compound_trigger.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/compound_triggers/static/js/compound_trigger.js new file mode 100644 index 000000000..109c33f24 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/compound_triggers/static/js/compound_trigger.js @@ -0,0 +1,412 @@ +///////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2019, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +////////////////////////////////////////////////////////////// + +define('pgadmin.node.compound_trigger', [ + 'sources/gettext', 'sources/url_for', 'jquery', 'underscore', + 'underscore.string', 'sources/pgadmin', 'pgadmin.browser', + 'pgadmin.backform', 'pgadmin.alertifyjs', + 'pgadmin.node.schema.dir/schema_child_tree_node', + 'pgadmin.browser.collection', +], function( + gettext, url_for, $, _, S, pgAdmin, pgBrowser, Backform, alertify, + SchemaChildTreeNode +) { + + if (!pgBrowser.Nodes['coll-compound_trigger']) { + pgAdmin.Browser.Nodes['coll-compound_trigger'] = + pgAdmin.Browser.Collection.extend({ + node: 'compound_trigger', + label: gettext('Compound Triggers'), + type: 'coll-compound_trigger', + columns: ['name', 'description'], + canDrop: SchemaChildTreeNode.isTreeItemOfChildOfSchema, + canDropCascade: SchemaChildTreeNode.isTreeItemOfChildOfSchema, + }); + } + + if (!pgBrowser.Nodes['compound_trigger']) { + pgAdmin.Browser.Nodes['compound_trigger'] = pgBrowser.Node.extend({ + parent_type: ['table', 'view', 'partition'], + collection_type: ['coll-table', 'coll-view'], + type: 'compound_trigger', + label: gettext('Compound Trigger'), + hasSQL: true, + hasDepends: true, + width: pgBrowser.stdW.sm + 'px', + sqlAlterHelp: 'sql-altertcompoundtrigger.html', + sqlCreateHelp: 'sql-createcompoundtrigger.html', + dialogHelp: url_for('help.static', {'filename': 'compound_trigger_dialog.html'}), + Init: function() { + /* Avoid mulitple registration of menus */ + if (this.initialized) + return; + + this.initialized = true; + + pgBrowser.add_menus([{ + name: 'create_compound_trigger_on_coll', node: 'coll-compound_trigger', module: this, + applies: ['object', 'context'], callback: 'show_obj_properties', + category: 'create', priority: 4, label: gettext('Compound Trigger...'), + icon: 'wcTabIcon icon-compound_trigger', data: {action: 'create', check: true}, + enable: 'canCreate', + },{ + name: 'create_compound_trigger', node: 'compound_trigger', module: this, + applies: ['object', 'context'], callback: 'show_obj_properties', + category: 'create', priority: 4, label: gettext('Compound Trigger...'), + icon: 'wcTabIcon icon-compound_trigger', data: {action: 'create', check: true}, + enable: 'canCreate', + },{ + name: 'create_compound_trigger_onTable', node: 'table', module: this, + applies: ['object', 'context'], callback: 'show_obj_properties', + category: 'create', priority: 4, label: gettext('Compound Trigger...'), + icon: 'wcTabIcon icon-compound_trigger', data: {action: 'create', check: true}, + enable: 'canCreate', + },{ + name: 'create_compound_trigger_onPartition', node: 'partition', module: this, + applies: ['object', 'context'], callback: 'show_obj_properties', + category: 'create', priority: 4, label: gettext('Compound Trigger...'), + icon: 'wcTabIcon icon-compound_trigger', data: {action: 'create', check: true}, + enable: 'canCreate', + },{ + name: 'enable_compound_trigger', node: 'compound_trigger', module: this, + applies: ['object', 'context'], callback: 'enable_compound_trigger', + category: 'connect', priority: 3, label: gettext('Enable compound trigger'), + icon: 'fa fa-check', enable : 'canCreate_with_compound_trigger_enable', + },{ + name: 'disable_compound_trigger', node: 'compound_trigger', module: this, + applies: ['object', 'context'], callback: 'disable_compound_trigger', + category: 'drop', priority: 3, label: gettext('Disable compound trigger'), + icon: 'fa fa-times', enable : 'canCreate_with_compound_trigger_disable', + },{ + name: 'create_compound_trigger_onView', node: 'view', module: this, + applies: ['object', 'context'], callback: 'show_obj_properties', + category: 'create', priority: 4, label: gettext('Compound Trigger...'), + icon: 'wcTabIcon icon-compound_trigger', data: {action: 'create', check: true}, + enable: 'canCreate', + }, + ]); + }, + callbacks: { + /* Enable compound trigger */ + enable_compound_trigger: function(args) { + var input = args || {}, + obj = this, + t = pgBrowser.tree, + i = input.item || t.selected(), + d = i && i.length == 1 ? t.itemData(i) : undefined; + + if (!d) + return false; + + var data = d; + $.ajax({ + url: obj.generate_url(i, 'enable' , d, true), + type:'PUT', + data: {'enable' : true}, + dataType: 'json', + }) + .done(function(res) { + if (res.success == 1) { + alertify.success(res.info); + t.removeIcon(i); + data.icon = 'icon-compound_trigger'; + t.addIcon(i, {icon: data.icon}); + t.unload(i); + t.setInode(false); + t.deselect(i); + // Fetch updated data from server + setTimeout(function() { + t.select(i); + }, 10); + } + }) + .fail(function(xhr, status, error) { + alertify.pgRespErrorNotify(xhr, error); + t.unload(i); + }); + }, + /* Disable compound trigger */ + disable_compound_trigger: function(args) { + var input = args || {}, + obj = this, + t = pgBrowser.tree, + i = input.item || t.selected(), + d = i && i.length == 1 ? t.itemData(i) : undefined; + + if (!d) + return false; + + var data = d; + $.ajax({ + url: obj.generate_url(i, 'enable' , d, true), + type:'PUT', + data: {'enable' : false}, + dataType: 'json', + }) + .done(function(res) { + if (res.success == 1) { + alertify.success(res.info); + t.removeIcon(i); + data.icon = 'icon-compound_trigger-bad'; + t.addIcon(i, {icon: data.icon}); + t.unload(i); + t.setInode(false); + t.deselect(i); + // Fetch updated data from server + setTimeout(function() { + t.select(i); + }, 10); + } + }) + .fail(function(xhr, status, error) { + alertify.pgRespErrorNotify(xhr, error, gettext('Disable compound trigger failed')); + t.unload(i); + }); + }, + }, + canDrop: SchemaChildTreeNode.isTreeItemOfChildOfSchema, + canDropCascade: SchemaChildTreeNode.isTreeItemOfChildOfSchema, + model: pgAdmin.Browser.Node.Model.extend({ + idAttribute: 'oid', + defaults: { + name: undefined, + }, + schema: [{ + id: 'name', label: gettext('Name'), cell: 'string', + type: 'text', disabled: 'inSchema', + },{ + id: 'oid', label: gettext('OID'), cell: 'string', + type: 'int', disabled: true, mode: ['properties'], + },{ + id: 'is_enable_trigger', label: gettext('Trigger enabled?'), + type: 'switch', disabled: 'inSchema', mode: ['edit', 'properties'], + },{ + type: 'nested', control: 'fieldset', mode: ['create','edit', 'properties'], + label: gettext('FOR Events'), group: gettext('Events'), contentClass: 'row', + schema:[{ + id: 'evnt_insert', label: gettext('INSERT'), + type: 'switch', mode: ['create','edit', 'properties'], + group: gettext('FOR Events'), + extraToggleClasses: 'pg-el-sm-6', + controlLabelClassName: 'control-label pg-el-sm-5 pg-el-12', + controlsClassName: 'pgadmin-controls pg-el-sm-7 pg-el-12', + disabled: function(m) { + var evn_insert = m.get('evnt_insert'); + if (!_.isUndefined(evn_insert) && m.node_info['server']['server_type'] == 'ppas') + return false; + return m.inSchemaWithModelCheck.apply(this, [m]); + }, + },{ + id: 'evnt_update', label: gettext('UPDATE'), + type: 'switch', mode: ['create','edit', 'properties'], + group: gettext('FOR Events'), + extraToggleClasses: 'pg-el-sm-6', + controlLabelClassName: 'control-label pg-el-sm-5 pg-el-12', + controlsClassName: 'pgadmin-controls pg-el-sm-7 pg-el-12', + disabled: function(m) { + var evn_update = m.get('evnt_update'); + if (!_.isUndefined(evn_update) && m.node_info['server']['server_type'] == 'ppas') + return false; + return m.inSchemaWithModelCheck.apply(this, [m]); + }, + },{ + id: 'evnt_delete', label: gettext('DELETE'), + type: 'switch', mode: ['create','edit', 'properties'], + group: gettext('FOR Events'), + extraToggleClasses: 'pg-el-sm-6', + controlLabelClassName: 'control-label pg-el-sm-5 pg-el-12', + controlsClassName: 'pgadmin-controls pg-el-sm-7 pg-el-12', + disabled: function(m) { + var evn_delete = m.get('evnt_delete'); + if (!_.isUndefined(evn_delete) && m.node_info['server']['server_type'] == 'ppas') + return false; + return m.inSchemaWithModelCheck.apply(this, [m]); + }, + }], + },{ + id: 'whenclause', label: gettext('When'), + type: 'text', disabled: 'inSchemaWithModelCheck', + mode: ['create', 'edit', 'properties'], + control: 'sql-field', visible: true, group: gettext('Events'), + },{ + id: 'columns', label: gettext('Columns'), url: 'nodes', + control: 'node-list-by-name', cache_node: 'column', type: 'array', + select2: {'multiple': true}, + deps: ['evnt_update'], node: 'column', group: gettext('Events'), + disabled: function(m) { + if(this.node_info && 'catalog' in this.node_info) { + return true; + } + //Disable in edit mode + if (!m.isNew()) { + return true; + } + // Enable column only if update event is set true + var isUpdate = m.get('evnt_update'); + if(!_.isUndefined(isUpdate) && isUpdate) { + return false; + } + return true; + }, + },{ + id: 'prosrc', label: gettext('Code'), group: gettext('Code'), + type: 'text', mode: ['create', 'edit'], + tabPanelCodeClass: 'sql-code-control', + control: Backform.SqlCodeControl, + disabled: function(m) { + if(m.isNew()) { + var code = m.getCodeTemplate(); + setTimeout(function() { + m.set('prosrc', code); + }, 10); + } + return false; + }, + },{ + id: 'is_sys_trigger', label: gettext('System trigger?'), cell: 'string', + type: 'switch', disabled: 'inSchemaWithModelCheck', mode: ['properties'], + },{ + id: 'description', label: gettext('Comment'), cell: 'string', + type: 'multiline', mode: ['properties', 'create', 'edit'], + disabled: 'inSchema', + }], + validate: function(keys) { + var msg; + this.errorModel.clear(); + + // If nothing to validate + if (keys && keys.length == 0) { + return null; + } + + if(_.isUndefined(this.get('name')) + || String(this.get('name')).replace(/^\s+|\s+$/g, '') == '') { + msg = gettext('Name cannot be empty.'); + this.errorModel.set('name', msg); + return msg; + } + + if(!this.get('evnt_truncate') && !this.get('evnt_delete') && + !this.get('evnt_update') && !this.get('evnt_insert')) { + msg = gettext('Specify at least one event.'); + this.errorModel.set('evnt_truncate', ' '); + this.errorModel.set('evnt_delete', ' '); + this.errorModel.set('evnt_update', ' '); + this.errorModel.set('evnt_insert', msg); + return msg; + } + + if(_.isUndefined(this.get('prosrc')) + || String(this.get('prosrc')).replace(/^\s+|\s+$/g, '') == '') { + msg = gettext('Code cannot be empty.'); + this.errorModel.set('prosrc', msg); + return msg; + } + + return null; + }, + // This function returns the code template for compound trigger + getCodeTemplate: function () { + return gettext('-- Enter any global declarations below:\n\n' + + '-- BEFORE STATEMENT block. Delete if not required.\n' + + 'BEFORE STATEMENT IS\n' + + ' -- Enter any local declarations here\n' + + 'BEGIN\n' + + ' -- Enter any required code here\n' + + 'END;\n\n' + + '-- AFTER STATEMENT block. Delete if not required.\n' + + 'AFTER STATEMENT IS\n' + + ' -- Enter any local declarations here\n' + + 'BEGIN\n' + + ' -- Enter any required code here\n' + + 'END;\n\n' + + '-- BEFORE EACH ROW block. Delete if not required.\n' + + 'BEFORE EACH ROW IS\n' + + ' -- Enter any local declarations here\n' + + 'BEGIN\n' + + ' -- Enter any required code here\n' + + 'END;\n\n' + + '-- AFTER EACH ROW block. Delete if not required.\n' + + 'AFTER EACH ROW IS\n' + + ' -- Enter any local declarations here\n' + + 'BEGIN\n' + + ' -- Enter any required code here\n' + + 'END;'); + }, + // We will check if we are under schema node & in 'create' mode + inSchema: function() { + if(this.node_info && 'catalog' in this.node_info) { + return true; + } + return false; + }, + // We will check if we are under schema node & in 'create' mode + inSchemaWithModelCheck: function(m) { + if(this.node_info && 'schema' in this.node_info) { + // We will disable control if it's in 'edit' mode + if (m.isNew()) { + return false; + } else { + return true; + } + } + return true; + }, + // Checks weather to enable/disable control + inSchemaWithColumnCheck: function(m) { + if(this.node_info && 'schema' in this.node_info) { + // We will disable control if it's system columns + // ie: it's position is less then 1 + if (m.isNew()) { + return false; + } else { + // if we are in edit mode + if (!_.isUndefined(m.get('attnum')) && m.get('attnum') >= 1 ) { + return false; + } else { + return true; + } + } + } + return true; + }, + }), + canCreate: function(itemData, item, data) { + //If check is false then , we will allow create menu + if (data && data.check == false) + return true; + + var treeData = this.getTreeNodeHierarchy(item), + server = treeData['server']; + + if (server && (server.server_type === 'pg' || server.version < 120000)) + return false; + + // If it is catalog then don't allow user to create package + if (treeData['catalog'] != undefined) + return false; + + // by default we want to allow create menu + return true; + }, + // Check to whether trigger is disable ? + canCreate_with_compound_trigger_enable: function(itemData, item, data) { + return itemData.icon === 'icon-compound_trigger-bad' && + this.canCreate.apply(this, [itemData, item, data]); + }, + // Check to whether trigger is enable ? + canCreate_with_compound_trigger_disable: function(itemData, item, data) { + return itemData.icon === 'icon-compound_trigger' && + this.canCreate.apply(this, [itemData, item, data]); + }, + }); + } + + return pgBrowser.Nodes['compound_trigger']; +}); diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/compound_triggers/templates/compound_triggers/css/compound_trigger.css b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/compound_triggers/templates/compound_triggers/css/compound_trigger.css new file mode 100644 index 000000000..198a1845a --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/compound_triggers/templates/compound_triggers/css/compound_trigger.css @@ -0,0 +1,23 @@ +.icon-coll-compound_trigger { + background-image: url('{{ url_for('NODE-compound_trigger.static', filename='img/coll-compound_trigger.svg' )}}') !important; + background-repeat: no-repeat; + background-size: 20px !important; + align-content: center; + vertical-align: middle; + height: 1.3em; +} + +.icon-compound_trigger { + background-image: url('{{ url_for('NODE-compound_trigger.static', filename='img/compound_trigger.svg') }}') !important; + background-repeat: no-repeat; + background-size: 20px !important; + align-content: center; + vertical-align: middle; + height: 1.3em; +} + +.icon-compound_trigger-bad { + background-image: url('{{ url_for('NODE-compound_trigger.static', filename='img/compound_trigger-bad.svg') }}') !important; + background-size: 20px !important; + border-radius: 10px +} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/compound_triggers/tests/__init__.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/compound_triggers/tests/__init__.py new file mode 100644 index 000000000..e9206708f --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/compound_triggers/tests/__init__.py @@ -0,0 +1,16 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2019, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + +from pgadmin.utils.route import BaseTestGenerator + + +class CompoundTriggersTestGenerator(BaseTestGenerator): + + def runTest(self): + return [] diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/compound_triggers/tests/ppas/12_plus/alter_event_comment_code.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/compound_triggers/tests/ppas/12_plus/alter_event_comment_code.sql new file mode 100644 index 000000000..f40affcf8 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/compound_triggers/tests/ppas/12_plus/alter_event_comment_code.sql @@ -0,0 +1,26 @@ +-- Compound Trigger: test_compound_trigger_$%{}[]()&*^!@"'`\/# + +-- DROP TRIGGER "test_compound_trigger_$%{}[]()&*^!@""'`\/#" ON testschema.table_for_compound_trigger; + +CREATE OR REPLACE TRIGGER "test_compound_trigger_$%{}[]()&*^!@""'`\/#" + FOR INSERT OR UPDATE + ON testschema.table_for_compound_trigger + WHEN ((new.id < 100)) + COMPOUND TRIGGER +var character varying(20) DEFAULT 'Global_var'; + +AFTER STATEMENT IS +BEGIN + DBMS_OUTPUT.PUT_LINE('After Statement: ' || var); + var := 'AFTER STATEMENT'; +END; + +AFTER EACH ROW IS +BEGIN + DBMS_OUTPUT.PUT_LINE('After each row: ' || var); + var := 'AFTER EACH ROW'; +END; +END "test_compound_trigger_$%{}[]()&*^!@""'`\/#"; + +COMMENT ON TRIGGER "test_compound_trigger_$%{}[]()&*^!@""'`\/#" ON testschema.table_for_compound_trigger + IS 'This is test comment.'; diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/compound_triggers/tests/ppas/12_plus/create_for_insert_delete.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/compound_triggers/tests/ppas/12_plus/create_for_insert_delete.sql new file mode 100644 index 000000000..4b60e7c21 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/compound_triggers/tests/ppas/12_plus/create_for_insert_delete.sql @@ -0,0 +1,13 @@ +-- Compound Trigger: test_compound_trigger_$%{}[]()&*^!@"'`\/# + +-- DROP TRIGGER "test_compound_trigger_$%{}[]()&*^!@""'`\/#" ON testschema.table_for_compound_trigger; + +CREATE OR REPLACE TRIGGER "test_compound_trigger_$%{}[]()&*^!@""'`\/#" + FOR INSERT OR DELETE + ON testschema.table_for_compound_trigger + COMPOUND TRIGGER +BEFORE STATEMENT IS +BEGIN + SELECT 1; +END; +END "test_compound_trigger_$%{}[]()&*^!@""'`\/#"; diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/compound_triggers/tests/ppas/12_plus/create_for_insert_update_on_columns.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/compound_triggers/tests/ppas/12_plus/create_for_insert_update_on_columns.sql new file mode 100644 index 000000000..874bbf154 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/compound_triggers/tests/ppas/12_plus/create_for_insert_update_on_columns.sql @@ -0,0 +1,22 @@ +-- Compound Trigger: test_compound_trigger_$%{}[]()&*^!@"'`\/# + +-- DROP TRIGGER "test_compound_trigger_$%{}[]()&*^!@""'`\/#" ON testschema.table_for_compound_trigger; + +CREATE OR REPLACE TRIGGER "test_compound_trigger_$%{}[]()&*^!@""'`\/#" + FOR INSERT OR UPDATE OF id, name + ON testschema.table_for_compound_trigger + COMPOUND TRIGGER +var character varying(20) DEFAULT 'Global_var'; + +BEFORE STATEMENT IS +BEGIN + DBMS_OUTPUT.PUT_LINE('Before Statement: ' || var); + var := 'BEFORE STATEMENT'; +END; + +BEFORE EACH ROW IS +BEGIN + DBMS_OUTPUT.PUT_LINE('Before each row: ' || var); + var := 'BEFORE EACH ROW'; +END; +END "test_compound_trigger_$%{}[]()&*^!@""'`\/#"; diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/compound_triggers/tests/ppas/12_plus/create_for_insert_with_when.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/compound_triggers/tests/ppas/12_plus/create_for_insert_with_when.sql new file mode 100644 index 000000000..773f82fff --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/compound_triggers/tests/ppas/12_plus/create_for_insert_with_when.sql @@ -0,0 +1,23 @@ +-- Compound Trigger: test_compound_trigger_$%{}[]()&*^!@"'`\/# + +-- DROP TRIGGER "test_compound_trigger_$%{}[]()&*^!@""'`\/#" ON testschema.table_for_compound_trigger; + +CREATE OR REPLACE TRIGGER "test_compound_trigger_$%{}[]()&*^!@""'`\/#" + FOR INSERT + ON testschema.table_for_compound_trigger + WHEN ((new.id < 100)) + COMPOUND TRIGGER +var character varying(20) DEFAULT 'Global_var'; + +BEFORE STATEMENT IS +BEGIN + DBMS_OUTPUT.PUT_LINE('Before Statement: ' || var); + var := 'BEFORE STATEMENT'; +END; + +BEFORE EACH ROW IS +BEGIN + DBMS_OUTPUT.PUT_LINE('Before each row: ' || var); + var := 'BEFORE EACH ROW'; +END; +END "test_compound_trigger_$%{}[]()&*^!@""'`\/#"; diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/compound_triggers/tests/ppas/12_plus/test.json b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/compound_triggers/tests/ppas/12_plus/test.json new file mode 100644 index 000000000..9037c195f --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/compound_triggers/tests/ppas/12_plus/test.json @@ -0,0 +1,99 @@ +{ + "scenarios": [ + { + "type": "create", + "name": "Create Table", + "endpoint": "NODE-table.obj", + "sql_endpoint": "NODE-table.sql_id", + "data": { + "name": "table_for_compound_trigger", + "columns": [{ + "name": "id", + "cltype": "integer", + "is_primary_key": true + }, { + "name": "name", + "cltype": "text" + }], + "is_partitioned": false, + "schema": "testschema", + "spcname": "pg_default" + }, + "store_table_id": true + }, { + "type": "create", + "name": "Create compound trigger for insert or delete", + "endpoint": "NODE-compound_trigger.obj", + "sql_endpoint": "NODE-compound_trigger.sql_id", + "data": { + "name": "test_compound_trigger_$%{}[]()&*^!@\"'`\\/#", + "prosrc": "BEFORE STATEMENT IS\nBEGIN\n\tSELECT 1;\nEND;", + "evnt_insert": true, + "evnt_update": false, + "evnt_delete": true, + "columns": ["id", "name"] + }, + "expected_sql_file": "create_for_insert_delete.sql" + }, { + "type": "delete", + "name": "Drop Compound Trigger", + "endpoint": "NODE-compound_trigger.delete_id", + "data": { + "name": "test_compound_trigger_$%{}[]()&*^!@\"'`\\/#" + } + }, { + "type": "create", + "name": "Create compound trigger for insert with when condition", + "endpoint": "NODE-compound_trigger.obj", + "sql_endpoint": "NODE-compound_trigger.sql_id", + "data": { + "name": "test_compound_trigger_$%{}[]()&*^!@\"'`\\/#", + "prosrc": "var varchar2(20) := 'Global_var';\n\nBEFORE STATEMENT IS\nBEGIN\n\tDBMS_OUTPUT.PUT_LINE('Before Statement: ' || var);\n\tvar := 'BEFORE STATEMENT';\nEND;\n\nBEFORE EACH ROW IS\nBEGIN\n\tDBMS_OUTPUT.PUT_LINE('Before each row: ' || var);\n\tvar := 'BEFORE EACH ROW';\nEND;", + "evnt_insert": true, + "evnt_update": false, + "evnt_delete": false, + "whenclause": "NEW.id < 100" + }, + "expected_sql_file": "create_for_insert_with_when.sql" + }, { + "type": "alter", + "name": "Alter event, comment and code", + "endpoint": "NODE-compound_trigger.obj_id", + "sql_endpoint": "NODE-compound_trigger.sql_id", + "data": { + "prosrc": "var varchar2(20) := 'Global_var';\n\nAFTER STATEMENT IS\nBEGIN\n\tDBMS_OUTPUT.PUT_LINE('After Statement: ' || var);\n\tvar := 'AFTER STATEMENT';\nEND;\n\nAFTER EACH ROW IS\nBEGIN\n\tDBMS_OUTPUT.PUT_LINE('After each row: ' || var);\n\tvar := 'AFTER EACH ROW';\nEND;", + "evnt_update": true, + "description": "This is test comment." + }, + "expected_sql_file": "alter_event_comment_code.sql" + }, { + "type": "delete", + "name": "Drop Compound Trigger", + "endpoint": "NODE-compound_trigger.delete_id", + "data": { + "name": "test_compound_trigger_$%{}[]()&*^!@\"'`\\/#" + } + }, { + "type": "create", + "name": "Create compound trigger for insert or update on columns", + "endpoint": "NODE-compound_trigger.obj", + "sql_endpoint": "NODE-compound_trigger.sql_id", + "data": { + "name": "test_compound_trigger_$%{}[]()&*^!@\"'`\\/#", + "prosrc": "var varchar2(20) := 'Global_var';\n\nBEFORE STATEMENT IS\nBEGIN\n\tDBMS_OUTPUT.PUT_LINE('Before Statement: ' || var);\n\tvar := 'BEFORE STATEMENT';\nEND;\n\nBEFORE EACH ROW IS\nBEGIN\n\tDBMS_OUTPUT.PUT_LINE('Before each row: ' || var);\n\tvar := 'BEFORE EACH ROW';\nEND;", + "evnt_insert": true, + "evnt_update": true, + "evnt_delete": false, + "columns": ["id", "name"] + }, + "expected_sql_file": "create_for_insert_update_on_columns.sql" + }, { + "type": "delete", + "name": "Drop Compound Trigger", + "endpoint": "NODE-compound_trigger.delete_id", + "data": { + "name": "test_compound_trigger_$%{}[]()&*^!@\"'`\\/#" + } + } + ] +} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/compound_triggers/tests/test_compound_triggers_add.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/compound_triggers/tests/test_compound_triggers_add.py new file mode 100644 index 000000000..ba839aeef --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/compound_triggers/tests/test_compound_triggers_add.py @@ -0,0 +1,134 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2019, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + +import json +import uuid + +from pgadmin.utils import server_utils as server_utils +from pgadmin.browser.server_groups.servers.databases.schemas.tables.tests \ + import utils as tables_utils +from pgadmin.browser.server_groups.servers.databases.schemas.tests import \ + utils as schema_utils +from pgadmin.browser.server_groups.servers.databases.tests import utils as \ + database_utils +from pgadmin.utils.route import BaseTestGenerator +from regression import parent_node_dict +from regression.python_test_utils import test_utils as utils + + +class CompoundTriggersAddTestCase(BaseTestGenerator): + """This class will add new compound trigger under table node.""" + skip_on_database = ['gpdb'] + scenarios = [ + ('Create compound trigger for insert and delete', + dict( + url='/browser/compound_trigger/obj/', + data={ + "prosrc": "var varchar2(20) := 'Global_var';\n\n" + "BEFORE STATEMENT IS\nBEGIN\n " + "DBMS_OUTPUT.PUT_LINE('Before Statement: ' || var)" + ";\n var := 'BEFORE STATEMENT';\nEND;\n\nBEFORE " + "EACH ROW IS\nBEGIN\n DBMS_OUTPUT.PUT_LINE('" + "Before each row: ' || var);\n var := 'BEFORE " + "EACH ROW';\nEND;", + "evnt_insert": True, + "evnt_update": False, + "evnt_delete": True + } + )), + ('Create compound trigger for insert with when condition', + dict( + url='/browser/compound_trigger/obj/', + data={ + "prosrc": "var varchar2(20) := 'Global_var';\n\n" + "BEFORE STATEMENT IS\nBEGIN\n " + "DBMS_OUTPUT.PUT_LINE('Before Statement: ' || var)" + ";\n var := 'BEFORE STATEMENT';\nEND;\n\nBEFORE " + "EACH ROW IS\nBEGIN\n DBMS_OUTPUT.PUT_LINE('" + "Before each row: ' || var);\n var := 'BEFORE " + "EACH ROW';\nEND;", + "evnt_insert": True, + "evnt_update": False, + "evnt_delete": False, + "whenclause": "NEW.id < 100" + } + )), + ('Create compound trigger for insert or update on columns', + dict( + url='/browser/compound_trigger/obj/', + data={ + "prosrc": "var varchar2(20) := 'Global_var';\n\n" + "BEFORE STATEMENT IS\nBEGIN\n " + "DBMS_OUTPUT.PUT_LINE('Before Statement: ' || var)" + ";\n var := 'BEFORE STATEMENT';\nEND;\n\nBEFORE " + "EACH ROW IS\nBEGIN\n DBMS_OUTPUT.PUT_LINE('" + "Before each row: ' || var);\n var := 'BEFORE " + "EACH ROW';\nEND;", + "evnt_insert": True, + "evnt_update": True, + "columns": ["id", "name"] + } + )), + ] + + def setUp(self): + super(CompoundTriggersAddTestCase, self).setUp() + self.db_name = parent_node_dict["database"][-1]["db_name"] + schema_info = parent_node_dict["schema"][-1] + self.server_id = schema_info["server_id"] + self.db_id = schema_info["db_id"] + server_con = server_utils.connect_server(self, self.server_id) + if server_con: + if "type" in server_con["data"]: + if server_con["data"]["type"] == "pg": + message = "Compound Triggers are not supported by PG." + self.skipTest(message) + elif server_con["data"]["type"] == "ppas" \ + and server_con["data"]["version"] < 120000: + message = "Compound Triggers are not supported by " \ + "EPAS server less than 12" + self.skipTest(message) + + db_con = database_utils.connect_database(self, utils.SERVER_GROUP, + self.server_id, self.db_id) + if not db_con['data']["connected"]: + raise Exception("Could not connect to database to add a trigger.") + self.schema_id = schema_info["schema_id"] + self.schema_name = schema_info["schema_name"] + schema_response = schema_utils.verify_schemas(self.server, + self.db_name, + self.schema_name) + if not schema_response: + raise Exception( + "Could not find the schema to add a compound trigger.") + self.table_name = \ + "table_compound_trigger_%s" % (str(uuid.uuid4())[1:8]) + self.table_id = tables_utils.create_table(self.server, self.db_name, + self.schema_name, + self.table_name) + + def runTest(self): + """This function will create compound trigger under table node.""" + trigger_name = \ + "test_compound_trigger_add_%s" % (str(uuid.uuid4())[1:8]) + + self.data.update({"name": trigger_name}) + + response = self.tester.post( + "{0}{1}/{2}/{3}/{4}/{5}/".format(self.url, utils.SERVER_GROUP, + self.server_id, self.db_id, + self.schema_id, self.table_id), + data=json.dumps(self.data), + content_type='html/json' + ) + self.assertEquals(response.status_code, 200) + + def tearDown(self): + # Disconnect the database + database_utils.disconnect_database(self, self.server_id, self.db_id) diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/compound_triggers/tests/test_compound_triggers_delete.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/compound_triggers/tests/test_compound_triggers_delete.py new file mode 100644 index 000000000..e55698566 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/compound_triggers/tests/test_compound_triggers_delete.py @@ -0,0 +1,98 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2019, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + +import uuid + +from pgadmin.utils import server_utils as server_utils +from pgadmin.browser.server_groups.servers.databases.schemas.tables.tests \ + import utils as tables_utils +from pgadmin.browser.server_groups.servers.databases.schemas.tests import \ + utils as schema_utils +from pgadmin.browser.server_groups.servers.databases.tests import utils as \ + database_utils +from pgadmin.utils.route import BaseTestGenerator +from regression import parent_node_dict +from regression.python_test_utils import test_utils as utils +from . import utils as compound_triggers_utils + + +class CompoundTriggersDeleteTestCase(BaseTestGenerator): + """This class will delete compound trigger under table node.""" + skip_on_database = ['gpdb'] + scenarios = [ + ('Delete compound trigger', + dict(url='/browser/compound_trigger/obj/')) + ] + + def setUp(self): + super(CompoundTriggersDeleteTestCase, self).setUp() + self.db_name = parent_node_dict["database"][-1]["db_name"] + schema_info = parent_node_dict["schema"][-1] + self.server_id = schema_info["server_id"] + self.db_id = schema_info["db_id"] + server_con = server_utils.connect_server(self, self.server_id) + if server_con: + if "type" in server_con["data"]: + if server_con["data"]["type"] == "pg": + message = "Compound Triggers are not supported by PG." + self.skipTest(message) + elif server_con["data"]["type"] == "ppas" \ + and server_con["data"]["version"] < 120000: + message = "Compound Triggers are not supported by " \ + "EPAS server less than 12" + self.skipTest(message) + + db_con = database_utils.connect_database(self, utils.SERVER_GROUP, + self.server_id, self.db_id) + if not db_con['data']["connected"]: + raise Exception( + "Could not connect to database to delete a compound trigger.") + self.schema_id = schema_info["schema_id"] + self.schema_name = schema_info["schema_name"] + schema_response = schema_utils.verify_schemas(self.server, + self.db_name, + self.schema_name) + if not schema_response: + raise Exception( + "Could not find the schema to delete a compound trigger.") + self.table_name = \ + "table_compound_trigger_%s" % (str(uuid.uuid4())[1:8]) + self.table_id = tables_utils.create_table(self.server, self.db_name, + self.schema_name, + self.table_name) + + self.trigger_name = \ + "test_compound_trigger_delete_%s" % (str(uuid.uuid4())[1:8]) + self.trigger_id = \ + compound_triggers_utils.create_compound_trigger(self.server, + self.db_name, + self.schema_name, + self.table_name, + self.trigger_name) + + def runTest(self): + """This function will update trigger under table node.""" + trigger_response = \ + compound_triggers_utils.verify_compound_trigger(self.server, + self.db_name, + self.trigger_name) + if not trigger_response: + raise Exception("Could not find the compound trigger to delete.") + response = self.tester.delete( + "{0}{1}/{2}/{3}/{4}/{5}/{6}".format(self.url, utils.SERVER_GROUP, + self.server_id, self.db_id, + self.schema_id, self.table_id, + self.trigger_id), + follow_redirects=True + ) + self.assertEquals(response.status_code, 200) + + def tearDown(self): + # Disconnect the database + database_utils.disconnect_database(self, self.server_id, self.db_id) diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/compound_triggers/tests/test_compound_triggers_delete_multiple.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/compound_triggers/tests/test_compound_triggers_delete_multiple.py new file mode 100644 index 000000000..d7019224b --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/compound_triggers/tests/test_compound_triggers_delete_multiple.py @@ -0,0 +1,112 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2019, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + +import uuid +import json + +from pgadmin.utils import server_utils as server_utils +from pgadmin.browser.server_groups.servers.databases.schemas.tables.tests \ + import utils as tables_utils +from pgadmin.browser.server_groups.servers.databases.schemas.tests import \ + utils as schema_utils +from pgadmin.browser.server_groups.servers.databases.tests import utils as \ + database_utils +from pgadmin.utils.route import BaseTestGenerator +from regression import parent_node_dict +from regression.python_test_utils import test_utils as utils +from . import utils as compound_triggers_utils + + +class CompoundTriggersDeleteMultipleTestCase(BaseTestGenerator): + """This class will delete multiple compound triggers under table node.""" + skip_on_database = ['gpdb'] + scenarios = [ + ('Delete multiple compound trigger', + dict(url='/browser/compound_trigger/obj/')) + ] + + def setUp(self): + super(CompoundTriggersDeleteMultipleTestCase, self).setUp() + self.db_name = parent_node_dict["database"][-1]["db_name"] + schema_info = parent_node_dict["schema"][-1] + self.server_id = schema_info["server_id"] + self.db_id = schema_info["db_id"] + server_con = server_utils.connect_server(self, self.server_id) + if server_con: + if "type" in server_con["data"]: + if server_con["data"]["type"] == "pg": + message = "Compound Triggers are not supported by PG." + self.skipTest(message) + elif server_con["data"]["type"] == "ppas" \ + and server_con["data"]["version"] < 120000: + message = "Compound Triggers are not supported by " \ + "EPAS server less than 12" + self.skipTest(message) + + db_con = database_utils.connect_database(self, utils.SERVER_GROUP, + self.server_id, self.db_id) + if not db_con['data']["connected"]: + raise Exception( + "Could not connect to database to delete a compound trigger.") + self.schema_id = schema_info["schema_id"] + self.schema_name = schema_info["schema_name"] + schema_response = schema_utils.verify_schemas(self.server, + self.db_name, + self.schema_name) + if not schema_response: + raise Exception( + "Could not find the schema to delete a compound trigger.") + self.table_name = \ + "table_compound_trigger_%s" % (str(uuid.uuid4())[1:8]) + self.table_id = tables_utils.create_table(self.server, self.db_name, + self.schema_name, + self.table_name) + + self.trigger_name = \ + "test_trigger_delete_%s" % (str(uuid.uuid4())[1:8]) + self.trigger_name_1 = \ + "test_trigger_delete_%s" % (str(uuid.uuid4())[1:8]) + self.trigger_ids = [compound_triggers_utils.create_compound_trigger( + self.server, self.db_name, self.schema_name, self.table_name, + self.trigger_name), + compound_triggers_utils.create_compound_trigger( + self.server, self.db_name, self.schema_name, self.table_name, + self.trigger_name_1) + ] + + def runTest(self): + """This function will delete multiple compound trigger under + table node.""" + trigger_response = \ + compound_triggers_utils.verify_compound_trigger( + self.server, self.db_name, self.trigger_name) + if not trigger_response: + raise Exception("Could not find the compound trigger to delete.") + + trigger_response = \ + compound_triggers_utils.verify_compound_trigger( + self.server, self.db_name, self.trigger_name_1) + if not trigger_response: + raise Exception("Could not find the compound trigger to delete.") + + data = {'ids': self.trigger_ids} + response = self.tester.delete( + "{0}{1}/{2}/{3}/{4}/{5}/".format(self.url, utils.SERVER_GROUP, + self.server_id, self.db_id, + self.schema_id, self.table_id + ), + follow_redirects=True, + data=json.dumps(data), + content_type='html/json' + ) + self.assertEquals(response.status_code, 200) + + def tearDown(self): + # Disconnect the database + database_utils.disconnect_database(self, self.server_id, self.db_id) diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/compound_triggers/tests/test_compound_triggers_put.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/compound_triggers/tests/test_compound_triggers_put.py new file mode 100644 index 000000000..526bd9840 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/compound_triggers/tests/test_compound_triggers_put.py @@ -0,0 +1,134 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2019, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + +import json +import uuid + +from pgadmin.utils import server_utils as server_utils +from pgadmin.browser.server_groups.servers.databases.schemas.tables.tests \ + import utils as tables_utils +from pgadmin.browser.server_groups.servers.databases.schemas.tests import \ + utils as schema_utils +from pgadmin.browser.server_groups.servers.databases.tests import utils as \ + database_utils +from pgadmin.utils.route import BaseTestGenerator +from regression import parent_node_dict +from regression.python_test_utils import test_utils as utils +from . import utils as compound_triggers_utils + + +class CompoundTriggersUpdateTestCase(BaseTestGenerator): + """This class will update compound trigger under table node.""" + skip_on_database = ['gpdb'] + scenarios = [ + ('Update comment', + dict(url='/browser/compound_trigger/obj/', + data={"description": "This is test comment."} + )), + ('Update event and code', + dict(url='/browser/compound_trigger/obj/', + data={ + "evnt_update": True, + "prosrc": "var varchar2(20) := 'Global_var';\n\n" + "AFTER STATEMENT IS\nBEGIN\n " + "DBMS_OUTPUT.PUT_LINE('After Statement: ' || var)" + ";\n var := 'AFTER STATEMENT';\nEND;\n\nAFTER " + "EACH ROW IS\nBEGIN\n DBMS_OUTPUT.PUT_LINE('" + "After each row: ' || var);\n var := 'AFTER " + "EACH ROW';\nEND;", + })), + ('Enable compound trigger', + dict(url='/browser/compound_trigger/obj/', + data={"is_enable_trigger": True}, + disable_trigger=True + )), + ('Disable compound trigger', + dict(url='/browser/compound_trigger/obj/', + data={"is_enable_trigger": False} + )), + ] + + def setUp(self): + super(CompoundTriggersUpdateTestCase, self).setUp() + self.db_name = parent_node_dict["database"][-1]["db_name"] + schema_info = parent_node_dict["schema"][-1] + self.server_id = schema_info["server_id"] + self.db_id = schema_info["db_id"] + server_con = server_utils.connect_server(self, self.server_id) + if server_con: + if "type" in server_con["data"]: + if server_con["data"]["type"] == "pg": + message = "Compound Triggers are not supported by PG." + self.skipTest(message) + elif server_con["data"]["type"] == "ppas" \ + and server_con["data"]["version"] < 120000: + message = "Compound Triggers are not supported by " \ + "EPAS server less than 12" + self.skipTest(message) + + db_con = database_utils.connect_database(self, utils.SERVER_GROUP, + self.server_id, self.db_id) + if not db_con['data']["connected"]: + raise Exception( + "Could not connect to database to update a compound trigger.") + self.schema_id = schema_info["schema_id"] + self.schema_name = schema_info["schema_name"] + schema_response = schema_utils.verify_schemas(self.server, + self.db_name, + self.schema_name) + if not schema_response: + raise Exception("Could not find the schema to update a trigger.") + self.table_name = \ + "table_compound_trigger_%s" % (str(uuid.uuid4())[1:8]) + self.table_id = tables_utils.create_table(self.server, self.db_name, + self.schema_name, + self.table_name) + + self.trigger_name = \ + "test_compound_trigger_update_%s" % (str(uuid.uuid4())[1:8]) + self.trigger_id = \ + compound_triggers_utils.create_compound_trigger(self.server, + self.db_name, + self.schema_name, + self.table_name, + self.trigger_name) + + def runTest(self): + """This function will update trigger under table node.""" + trigger_response = \ + compound_triggers_utils.verify_compound_trigger(self.server, + self.db_name, + self.trigger_name) + if not trigger_response: + raise Exception("Could not find the compound trigger to update.") + + if hasattr(self, 'disable_trigger') and self.disable_trigger: + compound_triggers_utils.enable_disable_compound_trigger( + self.server, + self.db_name, + self.schema_name, + self.table_name, + self.trigger_name, + False + ) + + self.data.update({"id": self.trigger_id}) + response = self.tester.put( + "{0}{1}/{2}/{3}/{4}/{5}/{6}".format(self.url, utils.SERVER_GROUP, + self.server_id, self.db_id, + self.schema_id, self.table_id, + self.trigger_id), + data=json.dumps(self.data), + follow_redirects=True + ) + self.assertEquals(response.status_code, 200) + + def tearDown(self): + # Disconnect the database + database_utils.disconnect_database(self, self.server_id, self.db_id) diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/compound_triggers/tests/utils.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/compound_triggers/tests/utils.py new file mode 100644 index 000000000..483d172d8 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/compound_triggers/tests/utils.py @@ -0,0 +1,137 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2019, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + +from __future__ import print_function + +import sys +import traceback + +from regression.python_test_utils import test_utils as utils + + +def create_compound_trigger(server, db_name, schema_name, table_name, + trigger_name): + """ + This function creates a column under provided table. + :param server: server details + :type server: dict + :param db_name: database name + :type db_name: str + :param schema_name: schema name + :type schema_name: str + :param table_name: table name + :type table_name: str + :param trigger_name: trigger name + :type trigger_name: str + :param trigger_func_name: trigger function name + :type trigger_func_name: str + :return trigger_id: trigger id + :rtype: int + """ + code = "var varchar2(20) := 'Global_var';\n\n BEFORE STATEMENT IS" \ + "\nBEGIN\n DBMS_OUTPUT.PUT_LINE('Before Statement: ' || var);" \ + "\n var := 'BEFORE STATEMENT';\nEND;\n\nBEFORE EACH ROW IS" \ + "\nBEGIN\n DBMS_OUTPUT.PUT_LINE('Before each row: ' || var);\n" \ + " var := 'BEFORE EACH ROW';\nEND;" + + try: + connection = utils.get_db_connection(db_name, + server['username'], + server['db_password'], + server['host'], + server['port'], + server['sslmode']) + old_isolation_level = connection.isolation_level + connection.set_isolation_level(0) + pg_cursor = connection.cursor() + query = "CREATE OR REPLACE TRIGGER %s FOR INSERT ON %s.%s " \ + "COMPOUND TRIGGER %s END;" % (trigger_name, schema_name, + table_name, code) + pg_cursor.execute(query) + connection.set_isolation_level(old_isolation_level) + connection.commit() + pg_cursor.execute("SELECT oid FROM pg_trigger where tgname='%s'" % + trigger_name) + trigger = pg_cursor.fetchone() + trigger_id = '' + if trigger: + trigger_id = trigger[0] + connection.close() + return trigger_id + except Exception: + traceback.print_exc(file=sys.stderr) + raise + + +def verify_compound_trigger(server, db_name, trigger_name): + """ + This function verifies table exist in database or not. + :param server: server details + :type server: dict + :param db_name: database name + :type db_name: str + :param trigger_name: column name + :type trigger_name: str + :return table: table record from database + :rtype: tuple + """ + try: + connection = utils.get_db_connection(db_name, + server['username'], + server['db_password'], + server['host'], + server['port'], + server['sslmode']) + pg_cursor = connection.cursor() + pg_cursor.execute("SELECT oid FROM pg_trigger where tgname='%s'" % + trigger_name) + trigger = pg_cursor.fetchone() + connection.close() + return trigger + except Exception: + traceback.print_exc(file=sys.stderr) + raise + + +def enable_disable_compound_trigger(server, db_name, schema_name, table_name, + trigger_name, is_enable): + """ + This function is used to enable/disable trigger. + :param server: + :param db_name: + :param schema_name: + :param table_name: + :param trigger_name: + :param is_enable: + :return: + """ + try: + connection = utils.get_db_connection(db_name, + server['username'], + server['db_password'], + server['host'], + server['port'], + server['sslmode']) + pg_cursor = connection.cursor() + pg_cursor.execute("BEGIN") + if is_enable: + query = "ALTER TABLE %s.%s ENABLE TRIGGER %s;" % (schema_name, + table_name, + trigger_name) + else: + query = "ALTER TABLE %s.%s DISABLE TRIGGER %s;" % (schema_name, + table_name, + trigger_name) + + pg_cursor.execute(query) + pg_cursor.execute("COMMIT") + connection.close() + except Exception: + traceback.print_exc(file=sys.stderr) + raise diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/default/backend_support.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/compound_triggers/sql/ppas/12_plus/backend_support.sql similarity index 100% rename from web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/default/backend_support.sql rename to web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/compound_triggers/sql/ppas/12_plus/backend_support.sql diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/compound_triggers/sql/ppas/12_plus/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/compound_triggers/sql/ppas/12_plus/create.sql new file mode 100644 index 000000000..2f9b392ab --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/compound_triggers/sql/ppas/12_plus/create.sql @@ -0,0 +1,26 @@ +{### Set a flag which allows us to put OR between events ###} +{% set or_flag = False %} +CREATE OR REPLACE TRIGGER {{ conn|qtIdent(data.name) }} + FOR {% if data.evnt_insert %}INSERT{% set or_flag = True %} +{% endif %}{% if data.evnt_delete %} +{% if or_flag %} OR {% endif %}DELETE{% set or_flag = True %} +{% endif %}{% if data.evnt_truncate %} +{% if or_flag %} OR {% endif %}TRUNCATE{% set or_flag = True %} +{% endif %}{% if data.evnt_update %} +{% if or_flag %} OR {% endif %}UPDATE{% if data.columns|length > 0 %} OF {% for c in data.columns %}{% if loop.index != 1 %}, {% endif %}{{ conn|qtIdent(c) }}{% endfor %}{% endif %} +{% endif %} + + ON {{ conn|qtIdent(data.schema, data.table) }} +{% if data.whenclause %} + WHEN {% if not data.oid %}({% endif %}{{ data.whenclause }}{% if not data.oid %}){% endif %} + +{% endif %} + COMPOUND TRIGGER +{% if data.prosrc is defined %}{{ data.prosrc }}{% endif%} + +END {{ conn|qtIdent(data.name) }}; + +{% if data.description %} +COMMENT ON TRIGGER {{ conn|qtIdent(data.name) }} ON {{ conn|qtIdent(data.schema, data.table) }} + IS {{data.description|qtLiteral}}; +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/default/delete.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/compound_triggers/sql/ppas/12_plus/delete.sql similarity index 100% rename from web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/default/delete.sql rename to web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/compound_triggers/sql/ppas/12_plus/delete.sql diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/default/enable_disable_trigger.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/compound_triggers/sql/ppas/12_plus/enable_disable_trigger.sql similarity index 100% rename from web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/default/enable_disable_trigger.sql rename to web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/compound_triggers/sql/ppas/12_plus/enable_disable_trigger.sql diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/default/get_columns.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/compound_triggers/sql/ppas/12_plus/get_columns.sql similarity index 100% rename from web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/default/get_columns.sql rename to web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/compound_triggers/sql/ppas/12_plus/get_columns.sql diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/default/get_oid.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/compound_triggers/sql/ppas/12_plus/get_oid.sql similarity index 100% rename from web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/default/get_oid.sql rename to web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/compound_triggers/sql/ppas/12_plus/get_oid.sql diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/default/get_parent.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/compound_triggers/sql/ppas/12_plus/get_parent.sql similarity index 100% rename from web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/default/get_parent.sql rename to web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/compound_triggers/sql/ppas/12_plus/get_parent.sql diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/compound_triggers/sql/ppas/12_plus/nodes.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/compound_triggers/sql/ppas/12_plus/nodes.sql new file mode 100644 index 000000000..6b9b084bd --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/compound_triggers/sql/ppas/12_plus/nodes.sql @@ -0,0 +1,10 @@ +SELECT t.oid, t.tgname as name, (CASE WHEN tgenabled = 'O' THEN true ElSE false END) AS is_enable_trigger +FROM pg_trigger t + + WHERE NOT tgisinternal + AND tgrelid = {{tid}}::OID + AND tgpackageoid != 0 +{% if trid %} + AND t.oid = {{trid}}::OID +{% endif %} + ORDER BY tgname; diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/compound_triggers/sql/ppas/12_plus/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/compound_triggers/sql/ppas/12_plus/properties.sql new file mode 100644 index 000000000..dae839728 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/compound_triggers/sql/ppas/12_plus/properties.sql @@ -0,0 +1,22 @@ +SELECT t.oid,t.tgname AS name, t.xmin, t.tgtype, t.tgattr, relname, + CASE WHEN relkind = 'r' THEN TRUE ELSE FALSE END AS parentistable, + nspname, des.description, + regexp_replace(regexp_replace(pg_get_triggerdef(t.oid), + 'CREATE TRIGGER (.*) FOR (.*) ON (.*) \nCOMPOUND TRIGGER (.*)\n', ''), '[\n]?END$', '' + ) AS prosrc, + COALESCE(substring(pg_get_triggerdef(t.oid), 'WHEN (.*) \nCOMPOUND'), NULL) AS whenclause, +{% if datlastsysoid %} + (CASE WHEN t.oid <= {{ datlastsysoid}}::oid THEN true ElSE false END) AS is_sys_trigger, +{% endif %} + (CASE WHEN tgenabled = 'O' THEN true ElSE false END) AS is_enable_trigger +FROM pg_trigger t + JOIN pg_class cl ON cl.oid=tgrelid + JOIN pg_namespace na ON na.oid=relnamespace + LEFT OUTER JOIN pg_description des ON (des.objoid=t.oid AND des.classoid='pg_trigger'::regclass) +WHERE NOT tgisinternal + AND tgrelid = {{tid}}::OID + AND tgpackageoid != 0 +{% if trid %} + AND t.oid = {{trid}}::OID +{% endif %} +ORDER BY tgname; diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/compound_triggers/sql/ppas/12_plus/update.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/compound_triggers/sql/ppas/12_plus/update.sql new file mode 100644 index 000000000..12e83a531 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/compound_triggers/sql/ppas/12_plus/update.sql @@ -0,0 +1,40 @@ +{% if data.name and o_data.name != data.name %} +ALTER TRIGGER {{ conn|qtIdent(o_data.name) }} ON {{ conn|qtIdent(o_data.nspname, o_data.relname) }} + RENAME TO {{ conn|qtIdent(data.name) }}; + +{% endif %} +{% if ((data.prosrc is defined or data.evnt_insert is defined or data.evnt_delete is defined or data.evnt_update is defined) and (o_data.prosrc != data.prosrc or data.evnt_insert != o_data.evnt_insert or data.evnt_delete != o_data.evnt_delete or data.evnt_update != o_data.evnt_update)) %} +{% set or_flag = False %} +CREATE OR REPLACE TRIGGER {{ conn|qtIdent(data.name) }} + FOR {% if data.evnt_insert is not defined %}{% if o_data.evnt_insert %}INSERT{% set or_flag = True %}{% endif %}{% if data.evnt_insert %}INSERT{% set or_flag = True %}{% endif %}{% endif %}{% if data.evnt_delete is not defined %}{% if o_data.evnt_delete %} +{% if or_flag %} OR {% endif %}DELETE{% set or_flag = True %} +{% endif %}{% else %}{% if data.evnt_delete %} +{% if or_flag %} OR {% endif %}DELETE{% set or_flag = True %}{%endif %}{% endif %}{% if data.evnt_update is not defined %}{% if o_data.evnt_update %} +{% if or_flag %} OR {% endif %}UPDATE{% if o_data.columns|length > 0 %} OF {% for c in o_data.columns %}{% if loop.index != 1 %}, {% endif %}{{ conn|qtIdent(c) }}{% endfor %}{% endif %} +{% endif %}{% else %}{% if data.evnt_update %} +{% if or_flag %} OR {% endif %}UPDATE{% if o_data.columns|length > 0 %} OF {% for c in o_data.columns %}{% if loop.index != 1 %}, {% endif %}{{ conn|qtIdent(c) }}{% endfor %}{% endif %}{% endif %} +{% endif %} + + ON {{ conn|qtIdent(data.schema, data.table) }} +{% if o_data.whenclause %} + WHEN {{ o_data.whenclause }} + +{% endif %} + COMPOUND TRIGGER +{% if (data.prosrc is not defined) %}{{ o_data.prosrc }}{% else %}{{ data.prosrc }}{% endif %} + +END {{ conn|qtIdent(data.name) }}; + +{% if data.description is not defined and o_data.description %} +COMMENT ON TRIGGER {{ conn|qtIdent(data.name) }} ON {{ conn|qtIdent(o_data.nspname, o_data.relname) }} + IS {{o_data.description|qtLiteral}}; +{% endif %} +{% endif %} +{% if data.description is defined and o_data.description != data.description %} +COMMENT ON TRIGGER {{ conn|qtIdent(data.name) }} ON {{ conn|qtIdent(o_data.nspname, o_data.relname) }} + IS {{data.description|qtLiteral}}; +{% endif %} +{% if data.is_enable_trigger is defined and o_data.is_enable_trigger != data.is_enable_trigger %} +ALTER TABLE {{ conn|qtIdent(o_data.nspname, o_data.relname) }} + {% if data.is_enable_trigger == True %}ENABLE{% else %}DISABLE{% endif %} TRIGGER {{ conn|qtIdent(data.name) }}; +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/9.1_plus/get_oid.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/gpdb/9.1_plus/get_oid.sql similarity index 100% rename from web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/9.1_plus/get_oid.sql rename to web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/gpdb/9.1_plus/get_oid.sql diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/9.1_plus/nodes.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/gpdb/9.1_plus/nodes.sql similarity index 100% rename from web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/9.1_plus/nodes.sql rename to web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/gpdb/9.1_plus/nodes.sql diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/9.1_plus/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/gpdb/9.1_plus/properties.sql similarity index 93% rename from web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/9.1_plus/properties.sql rename to web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/gpdb/9.1_plus/properties.sql index 2d34accfb..6d17cb65e 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/9.1_plus/properties.sql +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/gpdb/9.1_plus/properties.sql @@ -1,7 +1,7 @@ SELECT t.oid,t.tgname AS name, t.xmin, t.*, relname, CASE WHEN relkind = 'r' THEN TRUE ELSE FALSE END AS parentistable, nspname, des.description, l.lanname, p.prosrc, p.proname AS tfunction, COALESCE(substring(pg_get_triggerdef(t.oid), 'WHEN (.*) EXECUTE PROCEDURE'), - substring(pg_get_triggerdef(t.oid), 'WHEN (.*) \\$trigger')) AS whenclause, + substring(pg_get_triggerdef(t.oid), 'WHEN (.*) \$trigger')) AS whenclause, -- We need to convert tgargs column bytea datatype to array datatype (string_to_array(encode(tgargs, 'escape'), E'\\000')::text[])[1:tgnargs] AS custom_tgargs, {% if datlastsysoid %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/default/alter.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/gpdb/default/alter.sql similarity index 100% rename from web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/default/alter.sql rename to web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/gpdb/default/alter.sql diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/gpdb/default/backend_support.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/gpdb/default/backend_support.sql new file mode 100644 index 000000000..bb5e8d803 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/gpdb/default/backend_support.sql @@ -0,0 +1,9 @@ +{#=============Checks if it is materialized view========#} +{% if vid %} +SELECT + CASE WHEN c.relkind = 'm' THEN False ELSE True END As m_view +FROM + pg_class c +WHERE + c.oid = {{ vid }}::oid +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/default/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/gpdb/default/create.sql similarity index 100% rename from web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/default/create.sql rename to web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/gpdb/default/create.sql diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/gpdb/default/delete.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/gpdb/default/delete.sql new file mode 100644 index 000000000..4c6e82b28 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/gpdb/default/delete.sql @@ -0,0 +1 @@ +DROP TRIGGER {{conn|qtIdent(data.name)}} ON {{conn|qtIdent(data.nspname, data.relname )}}{% if cascade %} CASCADE{% endif %}; \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/gpdb/default/enable_disable_trigger.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/gpdb/default/enable_disable_trigger.sql new file mode 100644 index 000000000..b7009272d --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/gpdb/default/enable_disable_trigger.sql @@ -0,0 +1,2 @@ +ALTER TABLE {{ conn|qtIdent(data.schema, data.table) }} + {% if data.is_enable_trigger == True %}ENABLE{% else %}DISABLE{% endif %} TRIGGER {{ conn|qtIdent(data.name) }}; \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/gpdb/default/get_columns.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/gpdb/default/get_columns.sql new file mode 100644 index 000000000..c74c68b6a --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/gpdb/default/get_columns.sql @@ -0,0 +1,6 @@ +SELECT att.attname as name +FROM pg_attribute att + WHERE att.attrelid = {{tid}}::oid + AND att.attnum IN ({{ clist }}) + AND att.attisdropped IS FALSE + ORDER BY att.attnum \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/gpdb/default/get_oid.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/gpdb/default/get_oid.sql new file mode 100644 index 000000000..ff2638590 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/gpdb/default/get_oid.sql @@ -0,0 +1,4 @@ +SELECT t.oid +FROM pg_trigger t + WHERE tgrelid = {{tid}}::OID + AND tgname = {{data.name|qtLiteral}}; \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/gpdb/default/get_parent.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/gpdb/default/get_parent.sql new file mode 100644 index 000000000..f595cdc81 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/gpdb/default/get_parent.sql @@ -0,0 +1,5 @@ +SELECT nsp.nspname AS schema ,rel.relname AS table +FROM pg_class rel + JOIN pg_namespace nsp + ON rel.relnamespace = nsp.oid::oid + WHERE rel.oid = {{tid}}::oid \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/default/get_triggerfunctions.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/gpdb/default/get_triggerfunctions.sql similarity index 100% rename from web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/default/get_triggerfunctions.sql rename to web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/gpdb/default/get_triggerfunctions.sql diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/default/nodes.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/gpdb/default/nodes.sql similarity index 100% rename from web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/default/nodes.sql rename to web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/gpdb/default/nodes.sql diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/default/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/gpdb/default/properties.sql similarity index 100% rename from web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/default/properties.sql rename to web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/gpdb/default/properties.sql diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/default/update.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/gpdb/default/update.sql similarity index 81% rename from web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/default/update.sql rename to web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/gpdb/default/update.sql index e400cd6d8..05af50e27 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/default/update.sql +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/gpdb/default/update.sql @@ -6,16 +6,16 @@ ALTER TRIGGER {{ conn|qtIdent(o_data.name) }} ON {{ conn|qtIdent(o_data.nspname, {% if ((data.prosrc is defined or data.is_row_trigger is defined or data.evnt_insert is defined or data.evnt_delete is defined or data.evnt_update is defined or data.fires is defined) and o_data.lanname == 'edbspl' and (o_data.prosrc != data.prosrc or data.is_row_trigger != o_data.is_row_trigger or data.evnt_insert != o_data.evnt_insert or data.evnt_delete != o_data.evnt_delete or data.evnt_update != o_data.evnt_update or o_data.fires != data.fires)) %} {% set or_flag = False %} CREATE OR REPLACE TRIGGER {{ conn|qtIdent(data.name) }} - {% if data.fires is defined %} {{data.fires}} {% else %}{{o_data.fires}}{% endif %} {% if data.evnt_insert is not defined %} {% if o_data.evnt_insert %}INSERT{% set or_flag = True %} + {% if data.fires is defined %}{{data.fires}} {% else %}{{o_data.fires}} {% endif %}{% if data.evnt_insert is not defined %}{% if o_data.evnt_insert %}INSERT{% set or_flag = True %} {% endif %}{% else %}{% if data.evnt_insert %}INSERT{% set or_flag = True %}{% endif %}{% endif %}{% if data.evnt_delete is not defined %}{% if o_data.evnt_delete %} {% if or_flag %} OR {% endif %}DELETE{% set or_flag = True %} -{% endif %}{% else %} {% if data.evnt_delete %} -{% if or_flag %} OR {% endif %}DELETE{% set or_flag = True %} {%endif %}{% endif %}{% if data.evnt_truncate is not defined %}{% if o_data.evnt_truncate %} +{% endif %}{% else %}{% if data.evnt_delete %} +{% if or_flag %} OR {% endif %}DELETE{% set or_flag = True %}{%endif %}{% endif %}{% if data.evnt_truncate is not defined %}{% if o_data.evnt_truncate %} {% if or_flag %} OR {% endif %}TRUNCATE{% set or_flag = True %} -{% endif %}{% else %} {% if data.evnt_truncate %} -{% if or_flag %} OR {% endif %}TRUNCATE{% set or_flag = True %} {%endif %} {% endif %}{% if data.evnt_update is not defined %}{% if o_data.evnt_update %} +{% endif %}{% else %}{% if data.evnt_truncate %} +{% if or_flag %} OR {% endif %}TRUNCATE{% set or_flag = True %}{%endif %}{% endif %}{% if data.evnt_update is not defined %}{% if o_data.evnt_update %} {% if or_flag %} OR {% endif %}UPDATE {% if o_data.columns|length > 0 %}OF {% for c in o_data.columns %}{% if loop.index != 1 %}, {% endif %}{{ conn|qtIdent(c) }}{% endfor %}{% endif %} -{% endif %}{% else %} {% if data.evnt_update %} +{% endif %}{% else %}{% if data.evnt_update %} {% if or_flag %} OR {% endif %}UPDATE {% if o_data.columns|length > 0 %}OF {% for c in o_data.columns %}{% if loop.index != 1 %}, {% endif %}{{ conn|qtIdent(c) }}{% endfor %}{% endif %}{% endif %} {% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/10_plus/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/pg/10_plus/create.sql similarity index 100% rename from web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/10_plus/create.sql rename to web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/pg/10_plus/create.sql diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/10_plus/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/pg/10_plus/properties.sql similarity index 93% rename from web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/10_plus/properties.sql rename to web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/pg/10_plus/properties.sql index 28d10ad1e..3be4c82cd 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/10_plus/properties.sql +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/pg/10_plus/properties.sql @@ -1,7 +1,7 @@ SELECT t.oid,t.tgname AS name, t.xmin, t.*, relname, CASE WHEN relkind = 'r' THEN TRUE ELSE FALSE END AS parentistable, nspname, des.description, l.lanname, p.prosrc, p.proname AS tfunction, COALESCE(substring(pg_get_triggerdef(t.oid), 'WHEN (.*) EXECUTE PROCEDURE'), - substring(pg_get_triggerdef(t.oid), 'WHEN (.*) \\$trigger')) AS whenclause, + substring(pg_get_triggerdef(t.oid), 'WHEN (.*) \$trigger')) AS whenclause, -- We need to convert tgargs column bytea datatype to array datatype (string_to_array(encode(tgargs, 'escape'), E'\\000')::text[])[1:tgnargs] AS custom_tgargs, {% if datlastsysoid %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/pg/9.1_plus/get_oid.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/pg/9.1_plus/get_oid.sql new file mode 100644 index 000000000..cf30257bb --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/pg/9.1_plus/get_oid.sql @@ -0,0 +1,5 @@ +SELECT t.oid +FROM pg_trigger t + WHERE NOT tgisinternal + AND tgrelid = {{tid}}::OID + AND tgname = {{data.name|qtLiteral}}; \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/pg/9.1_plus/nodes.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/pg/9.1_plus/nodes.sql new file mode 100644 index 000000000..59372cce3 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/pg/9.1_plus/nodes.sql @@ -0,0 +1,9 @@ +SELECT t.oid, t.tgname as name, (CASE WHEN tgenabled = 'O' THEN true ElSE false END) AS is_enable_trigger +FROM pg_trigger t + + WHERE NOT tgisinternal + AND tgrelid = {{tid}}::OID +{% if trid %} + AND t.oid = {{trid}}::OID +{% endif %} + ORDER BY tgname; diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/pg/9.1_plus/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/pg/9.1_plus/properties.sql new file mode 100644 index 000000000..6d17cb65e --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/pg/9.1_plus/properties.sql @@ -0,0 +1,23 @@ +SELECT t.oid,t.tgname AS name, t.xmin, t.*, relname, CASE WHEN relkind = 'r' THEN TRUE ELSE FALSE END AS parentistable, + nspname, des.description, l.lanname, p.prosrc, p.proname AS tfunction, + COALESCE(substring(pg_get_triggerdef(t.oid), 'WHEN (.*) EXECUTE PROCEDURE'), + substring(pg_get_triggerdef(t.oid), 'WHEN (.*) \$trigger')) AS whenclause, + -- We need to convert tgargs column bytea datatype to array datatype + (string_to_array(encode(tgargs, 'escape'), E'\\000')::text[])[1:tgnargs] AS custom_tgargs, +{% if datlastsysoid %} + (CASE WHEN t.oid <= {{ datlastsysoid}}::oid THEN true ElSE false END) AS is_sys_trigger, +{% endif %} + (CASE WHEN tgconstraint != 0::OID THEN true ElSE false END) AS is_constraint_trigger, + (CASE WHEN tgenabled = 'O' THEN true ElSE false END) AS is_enable_trigger +FROM pg_trigger t + JOIN pg_class cl ON cl.oid=tgrelid + JOIN pg_namespace na ON na.oid=relnamespace + LEFT OUTER JOIN pg_description des ON (des.objoid=t.oid AND des.classoid='pg_trigger'::regclass) + LEFT OUTER JOIN pg_proc p ON p.oid=t.tgfoid + LEFT OUTER JOIN pg_language l ON l.oid=p.prolang +WHERE NOT tgisinternal + AND tgrelid = {{tid}}::OID +{% if trid %} + AND t.oid = {{trid}}::OID +{% endif %} +ORDER BY tgname; diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/pg/default/alter.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/pg/default/alter.sql new file mode 100644 index 000000000..93f323e1a --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/pg/default/alter.sql @@ -0,0 +1,9 @@ +{## Alter index to use cluster type ##} +{% if data.indisclustered %} +ALTER TABLE {{conn|qtIdent(data.schema, data.table)}} + CLUSTER ON {{conn|qtIdent(data.name)}}; +{% endif %} +{## Changes description ##} +{% if data.description %} +COMMENT ON INDEX {{conn|qtIdent(data.name)}} + IS {{data.description|qtLiteral}};{% endif %} \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/pg/default/backend_support.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/pg/default/backend_support.sql new file mode 100644 index 000000000..bb5e8d803 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/pg/default/backend_support.sql @@ -0,0 +1,9 @@ +{#=============Checks if it is materialized view========#} +{% if vid %} +SELECT + CASE WHEN c.relkind = 'm' THEN False ELSE True END As m_view +FROM + pg_class c +WHERE + c.oid = {{ vid }}::oid +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/pg/default/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/pg/default/create.sql new file mode 100644 index 000000000..4cffc920f --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/pg/default/create.sql @@ -0,0 +1,32 @@ +{### Set a flag which allows us to put OR between events ###} +{% set or_flag = False %} +{% if data.lanname == 'edbspl' or data.tfunction == 'Inline EDB-SPL' %} +CREATE OR REPLACE TRIGGER {{ conn|qtIdent(data.name) }} +{% else %} +CREATE{% if data.is_constraint_trigger %} CONSTRAINT{% endif %} TRIGGER {{ conn|qtIdent(data.name) }} +{% endif %} + {{data.fires}} {% if data.evnt_insert %}INSERT{% set or_flag = True %} +{% endif %}{% if data.evnt_delete %} +{% if or_flag %} OR {% endif %}DELETE{% set or_flag = True %} +{% endif %}{% if data.evnt_truncate %} +{% if or_flag %} OR {% endif %}TRUNCATE{% set or_flag = True %} +{% endif %}{% if data.evnt_update %} +{% if or_flag %} OR {% endif %}UPDATE {% if data.columns|length > 0 %}OF {% for c in data.columns %}{% if loop.index != 1 %}, {% endif %}{{ conn|qtIdent(c) }}{% endfor %}{% endif %} +{% endif %} + + ON {{ conn|qtIdent(data.schema, data.table) }} +{% if data.tgdeferrable %} + DEFERRABLE{% if data.tginitdeferred %} INITIALLY DEFERRED{% endif %} +{% endif %} + FOR EACH{% if data.is_row_trigger %} ROW{% else %} STATEMENT{% endif %} +{% if data.whenclause %} + + WHEN ({{ data.whenclause }}){% endif %} + + {% if data.prosrc is defined and + (data.lanname == 'edbspl' or data.tfunction == 'Inline EDB-SPL') %}{{ data.prosrc }}{% else %}EXECUTE PROCEDURE {{ data.tfunction }}{% if data.tgargs %}({{ data.tgargs }}){% else %}(){% endif%}{% endif%}; + +{% if data.description %} +COMMENT ON TRIGGER {{ conn|qtIdent(data.name) }} ON {{ conn|qtIdent(data.schema, data.table) }} + IS {{data.description|qtLiteral}}; +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/pg/default/delete.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/pg/default/delete.sql new file mode 100644 index 000000000..4c6e82b28 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/pg/default/delete.sql @@ -0,0 +1 @@ +DROP TRIGGER {{conn|qtIdent(data.name)}} ON {{conn|qtIdent(data.nspname, data.relname )}}{% if cascade %} CASCADE{% endif %}; \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/pg/default/enable_disable_trigger.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/pg/default/enable_disable_trigger.sql new file mode 100644 index 000000000..b7009272d --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/pg/default/enable_disable_trigger.sql @@ -0,0 +1,2 @@ +ALTER TABLE {{ conn|qtIdent(data.schema, data.table) }} + {% if data.is_enable_trigger == True %}ENABLE{% else %}DISABLE{% endif %} TRIGGER {{ conn|qtIdent(data.name) }}; \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/pg/default/get_columns.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/pg/default/get_columns.sql new file mode 100644 index 000000000..c74c68b6a --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/pg/default/get_columns.sql @@ -0,0 +1,6 @@ +SELECT att.attname as name +FROM pg_attribute att + WHERE att.attrelid = {{tid}}::oid + AND att.attnum IN ({{ clist }}) + AND att.attisdropped IS FALSE + ORDER BY att.attnum \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/pg/default/get_oid.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/pg/default/get_oid.sql new file mode 100644 index 000000000..ff2638590 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/pg/default/get_oid.sql @@ -0,0 +1,4 @@ +SELECT t.oid +FROM pg_trigger t + WHERE tgrelid = {{tid}}::OID + AND tgname = {{data.name|qtLiteral}}; \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/pg/default/get_parent.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/pg/default/get_parent.sql new file mode 100644 index 000000000..f595cdc81 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/pg/default/get_parent.sql @@ -0,0 +1,5 @@ +SELECT nsp.nspname AS schema ,rel.relname AS table +FROM pg_class rel + JOIN pg_namespace nsp + ON rel.relnamespace = nsp.oid::oid + WHERE rel.oid = {{tid}}::oid \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/pg/default/get_triggerfunctions.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/pg/default/get_triggerfunctions.sql new file mode 100644 index 000000000..403d5d91e --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/pg/default/get_triggerfunctions.sql @@ -0,0 +1,15 @@ +SELECT quote_ident(nspname) || '.' || quote_ident(proname) AS tfunctions +FROM pg_proc p, pg_namespace n, pg_language l + WHERE p.pronamespace = n.oid + AND p.prolang = l.oid + -- PGOID_TYPE_TRIGGER = 2279 + AND l.lanname != 'edbspl' AND prorettype = 2279 + -- If Show SystemObjects is not true + {% if not show_system_objects %} + AND (nspname NOT LIKE 'pg\_%' AND nspname NOT in ('information_schema')) + {% endif %} + -- Find function for specific OID + {% if tgfoid %} + AND p.oid = {{tgfoid}}::OID + {% endif %} + ORDER BY nspname ASC, proname ASC diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/pg/default/nodes.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/pg/default/nodes.sql new file mode 100644 index 000000000..2a10badf2 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/pg/default/nodes.sql @@ -0,0 +1,7 @@ +SELECT t.oid, t.tgname as name, (CASE WHEN tgenabled = 'O' THEN true ElSE false END) AS is_enable_trigger +FROM pg_trigger t + WHERE tgrelid = {{tid}}::OID +{% if trid %} + AND t.oid = {{trid}}::OID +{% endif %} + ORDER BY tgname; diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/pg/default/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/pg/default/properties.sql new file mode 100644 index 000000000..9af811859 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/pg/default/properties.sql @@ -0,0 +1 @@ +SELECT 1 WHERE 1 = 2; diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/pg/default/update.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/pg/default/update.sql new file mode 100644 index 000000000..05af50e27 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/pg/default/update.sql @@ -0,0 +1,50 @@ +{% if data.name and o_data.name != data.name %} +ALTER TRIGGER {{ conn|qtIdent(o_data.name) }} ON {{ conn|qtIdent(o_data.nspname, o_data.relname) }} + RENAME TO {{ conn|qtIdent(data.name) }}; + +{% endif %} +{% if ((data.prosrc is defined or data.is_row_trigger is defined or data.evnt_insert is defined or data.evnt_delete is defined or data.evnt_update is defined or data.fires is defined) and o_data.lanname == 'edbspl' and (o_data.prosrc != data.prosrc or data.is_row_trigger != o_data.is_row_trigger or data.evnt_insert != o_data.evnt_insert or data.evnt_delete != o_data.evnt_delete or data.evnt_update != o_data.evnt_update or o_data.fires != data.fires)) %} +{% set or_flag = False %} +CREATE OR REPLACE TRIGGER {{ conn|qtIdent(data.name) }} + {% if data.fires is defined %}{{data.fires}} {% else %}{{o_data.fires}} {% endif %}{% if data.evnt_insert is not defined %}{% if o_data.evnt_insert %}INSERT{% set or_flag = True %} +{% endif %}{% else %}{% if data.evnt_insert %}INSERT{% set or_flag = True %}{% endif %}{% endif %}{% if data.evnt_delete is not defined %}{% if o_data.evnt_delete %} +{% if or_flag %} OR {% endif %}DELETE{% set or_flag = True %} +{% endif %}{% else %}{% if data.evnt_delete %} +{% if or_flag %} OR {% endif %}DELETE{% set or_flag = True %}{%endif %}{% endif %}{% if data.evnt_truncate is not defined %}{% if o_data.evnt_truncate %} +{% if or_flag %} OR {% endif %}TRUNCATE{% set or_flag = True %} +{% endif %}{% else %}{% if data.evnt_truncate %} +{% if or_flag %} OR {% endif %}TRUNCATE{% set or_flag = True %}{%endif %}{% endif %}{% if data.evnt_update is not defined %}{% if o_data.evnt_update %} +{% if or_flag %} OR {% endif %}UPDATE {% if o_data.columns|length > 0 %}OF {% for c in o_data.columns %}{% if loop.index != 1 %}, {% endif %}{{ conn|qtIdent(c) }}{% endfor %}{% endif %} +{% endif %}{% else %}{% if data.evnt_update %} +{% if or_flag %} OR {% endif %}UPDATE {% if o_data.columns|length > 0 %}OF {% for c in o_data.columns %}{% if loop.index != 1 %}, {% endif %}{{ conn|qtIdent(c) }}{% endfor %}{% endif %}{% endif %} +{% endif %} + + ON {{ conn|qtIdent(data.schema, data.table) }} +{% if o_data.tgdeferrable %} + DEFERRABLE{% if o_data.tginitdeferred %} INITIALLY DEFERRED{% endif %} +{% endif %}{% if data.is_row_trigger is not defined %} + FOR EACH{% if o_data.is_row_trigger %} ROW{% else %} STATEMENT{% endif %} {% else %} + FOR EACH{% if data.is_row_trigger %} ROW{% else %} STATEMENT{% endif %} {% endif %} +{% if o_data.whenclause %} + WHEN {{ o_data.whenclause }} +{% endif %} + +{% if (data.prosrc is not defined) %} +{{ o_data.prosrc }}; +{% else %} +{{ data.prosrc }}; +{% endif %} + +{% if data.description is not defined and o_data.description %} +COMMENT ON TRIGGER {{ conn|qtIdent(data.name) }} ON {{ conn|qtIdent(o_data.nspname, o_data.relname) }} + IS {{o_data.description|qtLiteral}}; +{% endif %} +{% endif %} +{% if data.description is defined and o_data.description != data.description %} +COMMENT ON TRIGGER {{ conn|qtIdent(data.name) }} ON {{ conn|qtIdent(o_data.nspname, o_data.relname) }} + IS {{data.description|qtLiteral}}; +{% endif %} +{% if data.is_enable_trigger is defined and o_data.is_enable_trigger != data.is_enable_trigger %} +ALTER TABLE {{ conn|qtIdent(o_data.nspname, o_data.relname) }} + {% if data.is_enable_trigger == True %}ENABLE{% else %}DISABLE{% endif %} TRIGGER {{ conn|qtIdent(data.name) }}; +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/ppas/10_plus/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/ppas/10_plus/create.sql new file mode 100644 index 000000000..807abe45c --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/ppas/10_plus/create.sql @@ -0,0 +1,36 @@ +{### Set a flag which allows us to put OR between events ###} +{% set or_flag = False %} +{% if data.lanname == 'edbspl' or data.tfunction == 'Inline EDB-SPL' %} +CREATE OR REPLACE TRIGGER {{ conn|qtIdent(data.name) }} +{% else %} +CREATE{% if data.is_constraint_trigger %} CONSTRAINT{% endif %} TRIGGER {{ conn|qtIdent(data.name) }} +{% endif %} + {{data.fires}} {% if data.evnt_insert %}INSERT{% set or_flag = True %} +{% endif %}{% if data.evnt_delete %} +{% if or_flag %} OR {% endif %}DELETE{% set or_flag = True %} +{% endif %}{% if data.evnt_truncate %} +{% if or_flag %} OR {% endif %}TRUNCATE{% set or_flag = True %} +{% endif %}{% if data.evnt_update %} +{% if or_flag %} OR {% endif %}UPDATE {% if data.columns|length > 0 %}OF {% for c in data.columns %}{% if loop.index != 1 %}, {% endif %}{{ conn|qtIdent(c) }}{% endfor %}{% endif %} +{% endif %} + + ON {{ conn|qtIdent(data.schema, data.table) }} +{% if data.tgdeferrable %} + DEFERRABLE{% if data.tginitdeferred %} INITIALLY DEFERRED{% endif %} +{% endif %} +{% if data.tgoldtable or data.tgnewtable %} + REFERENCING{% if data.tgnewtable %} NEW TABLE AS {{ conn|qtIdent(data.tgnewtable) }}{% endif %}{% if data.tgoldtable %} OLD TABLE AS {{ conn|qtIdent(data.tgoldtable) }}{% endif %} + +{% endif %} + FOR EACH{% if data.is_row_trigger %} ROW{% else %} STATEMENT{% endif %} +{% if data.whenclause %} + + WHEN {% if not data.oid %}({% endif %}{{ data.whenclause }}{% if not data.oid %}){% endif %}{% endif %} + + {% if data.prosrc is defined and + (data.lanname == 'edbspl' or data.tfunction == 'Inline EDB-SPL') %}{{ data.prosrc }}{% else %}EXECUTE PROCEDURE {{ data.tfunction }}{% if data.tgargs %}({{ data.tgargs }}){% else %}(){% endif%}{% endif%}; + +{% if data.description %} +COMMENT ON TRIGGER {{ conn|qtIdent(data.name) }} ON {{ conn|qtIdent(data.schema, data.table) }} + IS {{data.description|qtLiteral}}; +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/ppas/10_plus/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/ppas/10_plus/properties.sql new file mode 100644 index 000000000..3be4c82cd --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/ppas/10_plus/properties.sql @@ -0,0 +1,25 @@ +SELECT t.oid,t.tgname AS name, t.xmin, t.*, relname, CASE WHEN relkind = 'r' THEN TRUE ELSE FALSE END AS parentistable, + nspname, des.description, l.lanname, p.prosrc, p.proname AS tfunction, + COALESCE(substring(pg_get_triggerdef(t.oid), 'WHEN (.*) EXECUTE PROCEDURE'), + substring(pg_get_triggerdef(t.oid), 'WHEN (.*) \$trigger')) AS whenclause, + -- We need to convert tgargs column bytea datatype to array datatype + (string_to_array(encode(tgargs, 'escape'), E'\\000')::text[])[1:tgnargs] AS custom_tgargs, +{% if datlastsysoid %} + (CASE WHEN t.oid <= {{ datlastsysoid}}::oid THEN true ElSE false END) AS is_sys_trigger, +{% endif %} + (CASE WHEN tgconstraint != 0::OID THEN true ElSE false END) AS is_constraint_trigger, + (CASE WHEN tgenabled = 'O' THEN true ElSE false END) AS is_enable_trigger, + tgoldtable, + tgnewtable +FROM pg_trigger t + JOIN pg_class cl ON cl.oid=tgrelid + JOIN pg_namespace na ON na.oid=relnamespace + LEFT OUTER JOIN pg_description des ON (des.objoid=t.oid AND des.classoid='pg_trigger'::regclass) + LEFT OUTER JOIN pg_proc p ON p.oid=t.tgfoid + LEFT OUTER JOIN pg_language l ON l.oid=p.prolang +WHERE NOT tgisinternal + AND tgrelid = {{tid}}::OID +{% if trid %} + AND t.oid = {{trid}}::OID +{% endif %} +ORDER BY tgname; diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/ppas/12_plus/nodes.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/ppas/12_plus/nodes.sql new file mode 100644 index 000000000..b625ff433 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/ppas/12_plus/nodes.sql @@ -0,0 +1,10 @@ +SELECT t.oid, t.tgname as name, (CASE WHEN tgenabled = 'O' THEN true ElSE false END) AS is_enable_trigger +FROM pg_trigger t + + WHERE NOT tgisinternal + AND tgrelid = {{tid}}::OID + AND tgpackageoid = 0 +{% if trid %} + AND t.oid = {{trid}}::OID +{% endif %} + ORDER BY tgname; diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/ppas/12_plus/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/ppas/12_plus/properties.sql new file mode 100644 index 000000000..8c61f5cf3 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/ppas/12_plus/properties.sql @@ -0,0 +1,26 @@ +SELECT t.oid,t.tgname AS name, t.xmin, t.*, relname, CASE WHEN relkind = 'r' THEN TRUE ELSE FALSE END AS parentistable, + nspname, des.description, l.lanname, p.prosrc, p.proname AS tfunction, + COALESCE(substring(pg_get_triggerdef(t.oid), 'WHEN (.*) EXECUTE PROCEDURE'), + substring(pg_get_triggerdef(t.oid), 'WHEN (.*) \$trigger')) AS whenclause, + -- We need to convert tgargs column bytea datatype to array datatype + (string_to_array(encode(tgargs, 'escape'), E'\\000')::text[])[1:tgnargs] AS custom_tgargs, +{% if datlastsysoid %} + (CASE WHEN t.oid <= {{ datlastsysoid}}::oid THEN true ElSE false END) AS is_sys_trigger, +{% endif %} + (CASE WHEN tgconstraint != 0::OID THEN true ElSE false END) AS is_constraint_trigger, + (CASE WHEN tgenabled = 'O' THEN true ElSE false END) AS is_enable_trigger, + tgoldtable, + tgnewtable +FROM pg_trigger t + JOIN pg_class cl ON cl.oid=tgrelid + JOIN pg_namespace na ON na.oid=relnamespace + LEFT OUTER JOIN pg_description des ON (des.objoid=t.oid AND des.classoid='pg_trigger'::regclass) + LEFT OUTER JOIN pg_proc p ON p.oid=t.tgfoid + LEFT OUTER JOIN pg_language l ON l.oid=p.prolang +WHERE NOT tgisinternal + AND tgrelid = {{tid}}::OID + AND tgpackageoid = 0 +{% if trid %} + AND t.oid = {{trid}}::OID +{% endif %} +ORDER BY tgname; diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/ppas/9.1_plus/get_oid.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/ppas/9.1_plus/get_oid.sql new file mode 100644 index 000000000..cf30257bb --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/ppas/9.1_plus/get_oid.sql @@ -0,0 +1,5 @@ +SELECT t.oid +FROM pg_trigger t + WHERE NOT tgisinternal + AND tgrelid = {{tid}}::OID + AND tgname = {{data.name|qtLiteral}}; \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/ppas/9.1_plus/nodes.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/ppas/9.1_plus/nodes.sql new file mode 100644 index 000000000..59372cce3 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/ppas/9.1_plus/nodes.sql @@ -0,0 +1,9 @@ +SELECT t.oid, t.tgname as name, (CASE WHEN tgenabled = 'O' THEN true ElSE false END) AS is_enable_trigger +FROM pg_trigger t + + WHERE NOT tgisinternal + AND tgrelid = {{tid}}::OID +{% if trid %} + AND t.oid = {{trid}}::OID +{% endif %} + ORDER BY tgname; diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/ppas/9.1_plus/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/ppas/9.1_plus/properties.sql new file mode 100644 index 000000000..6d17cb65e --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/ppas/9.1_plus/properties.sql @@ -0,0 +1,23 @@ +SELECT t.oid,t.tgname AS name, t.xmin, t.*, relname, CASE WHEN relkind = 'r' THEN TRUE ELSE FALSE END AS parentistable, + nspname, des.description, l.lanname, p.prosrc, p.proname AS tfunction, + COALESCE(substring(pg_get_triggerdef(t.oid), 'WHEN (.*) EXECUTE PROCEDURE'), + substring(pg_get_triggerdef(t.oid), 'WHEN (.*) \$trigger')) AS whenclause, + -- We need to convert tgargs column bytea datatype to array datatype + (string_to_array(encode(tgargs, 'escape'), E'\\000')::text[])[1:tgnargs] AS custom_tgargs, +{% if datlastsysoid %} + (CASE WHEN t.oid <= {{ datlastsysoid}}::oid THEN true ElSE false END) AS is_sys_trigger, +{% endif %} + (CASE WHEN tgconstraint != 0::OID THEN true ElSE false END) AS is_constraint_trigger, + (CASE WHEN tgenabled = 'O' THEN true ElSE false END) AS is_enable_trigger +FROM pg_trigger t + JOIN pg_class cl ON cl.oid=tgrelid + JOIN pg_namespace na ON na.oid=relnamespace + LEFT OUTER JOIN pg_description des ON (des.objoid=t.oid AND des.classoid='pg_trigger'::regclass) + LEFT OUTER JOIN pg_proc p ON p.oid=t.tgfoid + LEFT OUTER JOIN pg_language l ON l.oid=p.prolang +WHERE NOT tgisinternal + AND tgrelid = {{tid}}::OID +{% if trid %} + AND t.oid = {{trid}}::OID +{% endif %} +ORDER BY tgname; diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/ppas/default/alter.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/ppas/default/alter.sql new file mode 100644 index 000000000..93f323e1a --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/ppas/default/alter.sql @@ -0,0 +1,9 @@ +{## Alter index to use cluster type ##} +{% if data.indisclustered %} +ALTER TABLE {{conn|qtIdent(data.schema, data.table)}} + CLUSTER ON {{conn|qtIdent(data.name)}}; +{% endif %} +{## Changes description ##} +{% if data.description %} +COMMENT ON INDEX {{conn|qtIdent(data.name)}} + IS {{data.description|qtLiteral}};{% endif %} \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/ppas/default/backend_support.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/ppas/default/backend_support.sql new file mode 100644 index 000000000..bb5e8d803 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/ppas/default/backend_support.sql @@ -0,0 +1,9 @@ +{#=============Checks if it is materialized view========#} +{% if vid %} +SELECT + CASE WHEN c.relkind = 'm' THEN False ELSE True END As m_view +FROM + pg_class c +WHERE + c.oid = {{ vid }}::oid +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/ppas/default/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/ppas/default/create.sql new file mode 100644 index 000000000..4cffc920f --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/ppas/default/create.sql @@ -0,0 +1,32 @@ +{### Set a flag which allows us to put OR between events ###} +{% set or_flag = False %} +{% if data.lanname == 'edbspl' or data.tfunction == 'Inline EDB-SPL' %} +CREATE OR REPLACE TRIGGER {{ conn|qtIdent(data.name) }} +{% else %} +CREATE{% if data.is_constraint_trigger %} CONSTRAINT{% endif %} TRIGGER {{ conn|qtIdent(data.name) }} +{% endif %} + {{data.fires}} {% if data.evnt_insert %}INSERT{% set or_flag = True %} +{% endif %}{% if data.evnt_delete %} +{% if or_flag %} OR {% endif %}DELETE{% set or_flag = True %} +{% endif %}{% if data.evnt_truncate %} +{% if or_flag %} OR {% endif %}TRUNCATE{% set or_flag = True %} +{% endif %}{% if data.evnt_update %} +{% if or_flag %} OR {% endif %}UPDATE {% if data.columns|length > 0 %}OF {% for c in data.columns %}{% if loop.index != 1 %}, {% endif %}{{ conn|qtIdent(c) }}{% endfor %}{% endif %} +{% endif %} + + ON {{ conn|qtIdent(data.schema, data.table) }} +{% if data.tgdeferrable %} + DEFERRABLE{% if data.tginitdeferred %} INITIALLY DEFERRED{% endif %} +{% endif %} + FOR EACH{% if data.is_row_trigger %} ROW{% else %} STATEMENT{% endif %} +{% if data.whenclause %} + + WHEN ({{ data.whenclause }}){% endif %} + + {% if data.prosrc is defined and + (data.lanname == 'edbspl' or data.tfunction == 'Inline EDB-SPL') %}{{ data.prosrc }}{% else %}EXECUTE PROCEDURE {{ data.tfunction }}{% if data.tgargs %}({{ data.tgargs }}){% else %}(){% endif%}{% endif%}; + +{% if data.description %} +COMMENT ON TRIGGER {{ conn|qtIdent(data.name) }} ON {{ conn|qtIdent(data.schema, data.table) }} + IS {{data.description|qtLiteral}}; +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/ppas/default/delete.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/ppas/default/delete.sql new file mode 100644 index 000000000..4c6e82b28 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/ppas/default/delete.sql @@ -0,0 +1 @@ +DROP TRIGGER {{conn|qtIdent(data.name)}} ON {{conn|qtIdent(data.nspname, data.relname )}}{% if cascade %} CASCADE{% endif %}; \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/ppas/default/enable_disable_trigger.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/ppas/default/enable_disable_trigger.sql new file mode 100644 index 000000000..b7009272d --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/ppas/default/enable_disable_trigger.sql @@ -0,0 +1,2 @@ +ALTER TABLE {{ conn|qtIdent(data.schema, data.table) }} + {% if data.is_enable_trigger == True %}ENABLE{% else %}DISABLE{% endif %} TRIGGER {{ conn|qtIdent(data.name) }}; \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/ppas/default/get_columns.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/ppas/default/get_columns.sql new file mode 100644 index 000000000..c74c68b6a --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/ppas/default/get_columns.sql @@ -0,0 +1,6 @@ +SELECT att.attname as name +FROM pg_attribute att + WHERE att.attrelid = {{tid}}::oid + AND att.attnum IN ({{ clist }}) + AND att.attisdropped IS FALSE + ORDER BY att.attnum \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/ppas/default/get_oid.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/ppas/default/get_oid.sql new file mode 100644 index 000000000..ff2638590 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/ppas/default/get_oid.sql @@ -0,0 +1,4 @@ +SELECT t.oid +FROM pg_trigger t + WHERE tgrelid = {{tid}}::OID + AND tgname = {{data.name|qtLiteral}}; \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/ppas/default/get_parent.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/ppas/default/get_parent.sql new file mode 100644 index 000000000..f595cdc81 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/ppas/default/get_parent.sql @@ -0,0 +1,5 @@ +SELECT nsp.nspname AS schema ,rel.relname AS table +FROM pg_class rel + JOIN pg_namespace nsp + ON rel.relnamespace = nsp.oid::oid + WHERE rel.oid = {{tid}}::oid \ No newline at end of file diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/ppas/default/get_triggerfunctions.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/ppas/default/get_triggerfunctions.sql new file mode 100644 index 000000000..403d5d91e --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/ppas/default/get_triggerfunctions.sql @@ -0,0 +1,15 @@ +SELECT quote_ident(nspname) || '.' || quote_ident(proname) AS tfunctions +FROM pg_proc p, pg_namespace n, pg_language l + WHERE p.pronamespace = n.oid + AND p.prolang = l.oid + -- PGOID_TYPE_TRIGGER = 2279 + AND l.lanname != 'edbspl' AND prorettype = 2279 + -- If Show SystemObjects is not true + {% if not show_system_objects %} + AND (nspname NOT LIKE 'pg\_%' AND nspname NOT in ('information_schema')) + {% endif %} + -- Find function for specific OID + {% if tgfoid %} + AND p.oid = {{tgfoid}}::OID + {% endif %} + ORDER BY nspname ASC, proname ASC diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/ppas/default/nodes.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/ppas/default/nodes.sql new file mode 100644 index 000000000..2a10badf2 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/ppas/default/nodes.sql @@ -0,0 +1,7 @@ +SELECT t.oid, t.tgname as name, (CASE WHEN tgenabled = 'O' THEN true ElSE false END) AS is_enable_trigger +FROM pg_trigger t + WHERE tgrelid = {{tid}}::OID +{% if trid %} + AND t.oid = {{trid}}::OID +{% endif %} + ORDER BY tgname; diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/ppas/default/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/ppas/default/properties.sql new file mode 100644 index 000000000..9af811859 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/ppas/default/properties.sql @@ -0,0 +1 @@ +SELECT 1 WHERE 1 = 2; diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/ppas/default/update.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/ppas/default/update.sql new file mode 100644 index 000000000..05af50e27 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/triggers/sql/ppas/default/update.sql @@ -0,0 +1,50 @@ +{% if data.name and o_data.name != data.name %} +ALTER TRIGGER {{ conn|qtIdent(o_data.name) }} ON {{ conn|qtIdent(o_data.nspname, o_data.relname) }} + RENAME TO {{ conn|qtIdent(data.name) }}; + +{% endif %} +{% if ((data.prosrc is defined or data.is_row_trigger is defined or data.evnt_insert is defined or data.evnt_delete is defined or data.evnt_update is defined or data.fires is defined) and o_data.lanname == 'edbspl' and (o_data.prosrc != data.prosrc or data.is_row_trigger != o_data.is_row_trigger or data.evnt_insert != o_data.evnt_insert or data.evnt_delete != o_data.evnt_delete or data.evnt_update != o_data.evnt_update or o_data.fires != data.fires)) %} +{% set or_flag = False %} +CREATE OR REPLACE TRIGGER {{ conn|qtIdent(data.name) }} + {% if data.fires is defined %}{{data.fires}} {% else %}{{o_data.fires}} {% endif %}{% if data.evnt_insert is not defined %}{% if o_data.evnt_insert %}INSERT{% set or_flag = True %} +{% endif %}{% else %}{% if data.evnt_insert %}INSERT{% set or_flag = True %}{% endif %}{% endif %}{% if data.evnt_delete is not defined %}{% if o_data.evnt_delete %} +{% if or_flag %} OR {% endif %}DELETE{% set or_flag = True %} +{% endif %}{% else %}{% if data.evnt_delete %} +{% if or_flag %} OR {% endif %}DELETE{% set or_flag = True %}{%endif %}{% endif %}{% if data.evnt_truncate is not defined %}{% if o_data.evnt_truncate %} +{% if or_flag %} OR {% endif %}TRUNCATE{% set or_flag = True %} +{% endif %}{% else %}{% if data.evnt_truncate %} +{% if or_flag %} OR {% endif %}TRUNCATE{% set or_flag = True %}{%endif %}{% endif %}{% if data.evnt_update is not defined %}{% if o_data.evnt_update %} +{% if or_flag %} OR {% endif %}UPDATE {% if o_data.columns|length > 0 %}OF {% for c in o_data.columns %}{% if loop.index != 1 %}, {% endif %}{{ conn|qtIdent(c) }}{% endfor %}{% endif %} +{% endif %}{% else %}{% if data.evnt_update %} +{% if or_flag %} OR {% endif %}UPDATE {% if o_data.columns|length > 0 %}OF {% for c in o_data.columns %}{% if loop.index != 1 %}, {% endif %}{{ conn|qtIdent(c) }}{% endfor %}{% endif %}{% endif %} +{% endif %} + + ON {{ conn|qtIdent(data.schema, data.table) }} +{% if o_data.tgdeferrable %} + DEFERRABLE{% if o_data.tginitdeferred %} INITIALLY DEFERRED{% endif %} +{% endif %}{% if data.is_row_trigger is not defined %} + FOR EACH{% if o_data.is_row_trigger %} ROW{% else %} STATEMENT{% endif %} {% else %} + FOR EACH{% if data.is_row_trigger %} ROW{% else %} STATEMENT{% endif %} {% endif %} +{% if o_data.whenclause %} + WHEN {{ o_data.whenclause }} +{% endif %} + +{% if (data.prosrc is not defined) %} +{{ o_data.prosrc }}; +{% else %} +{{ data.prosrc }}; +{% endif %} + +{% if data.description is not defined and o_data.description %} +COMMENT ON TRIGGER {{ conn|qtIdent(data.name) }} ON {{ conn|qtIdent(o_data.nspname, o_data.relname) }} + IS {{o_data.description|qtLiteral}}; +{% endif %} +{% endif %} +{% if data.description is defined and o_data.description != data.description %} +COMMENT ON TRIGGER {{ conn|qtIdent(data.name) }} ON {{ conn|qtIdent(o_data.nspname, o_data.relname) }} + IS {{data.description|qtLiteral}}; +{% endif %} +{% if data.is_enable_trigger is defined and o_data.is_enable_trigger != data.is_enable_trigger %} +ALTER TABLE {{ conn|qtIdent(o_data.nspname, o_data.relname) }} + {% if data.is_enable_trigger == True %}ENABLE{% else %}DISABLE{% endif %} TRIGGER {{ conn|qtIdent(data.name) }}; +{% endif %} diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/tests/test_trigger_get_oid_sql.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/tests/test_trigger_get_oid_sql.py index 1f29fad85..083acef74 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/tests/test_trigger_get_oid_sql.py +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/tests/test_trigger_get_oid_sql.py @@ -37,6 +37,10 @@ class TestTriggerGetOidSql(SQLTemplateTestBase): def generate_sql(self, version): file_path = os.path.join(os.path.dirname(__file__), "..", "templates", "triggers", "sql") + if 'type' in self.server: + file_path = os.path.join(os.path.dirname(__file__), "..", + "templates", + "triggers", "sql", self.server['type']) template_file = self.get_template_file(version, file_path, "get_oid.sql") jinja2.filters.FILTERS['qtLiteral'] = lambda value: "NULL" diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/tests/test_trigger_nodes_sql.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/tests/test_trigger_nodes_sql.py index 92e80dc93..fef6e667b 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/tests/test_trigger_nodes_sql.py +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/tests/test_trigger_nodes_sql.py @@ -32,6 +32,10 @@ class TestTriggerNodesSql(SQLTemplateTestBase): def generate_sql(self, version): file_path = os.path.join(os.path.dirname(__file__), "..", "templates", "triggers", "sql") + if 'type' in self.server: + file_path = os.path.join(os.path.dirname(__file__), "..", + "templates", + "triggers", "sql", self.server['type']) template_file = self.get_template_file(version, file_path, "nodes.sql") template = file_as_template(template_file) diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/tests/test_utils.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/tests/test_utils.py index 169cdf1d6..f84ec3d14 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/tests/test_utils.py +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/tests/test_utils.py @@ -69,4 +69,4 @@ class TestUtils(BaseTestGenerator): 'indexes/sql/#gpdb#10#') self.assertEqual( subject.trigger_template_path, - 'triggers/sql/#gpdb#10#') + 'triggers/sql/gpdb/#10#') diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/triggers/__init__.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/triggers/__init__.py index c64b3c482..191bc3820 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/triggers/__init__.py +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/triggers/__init__.py @@ -78,7 +78,8 @@ class TriggerModule(CollectionNodeModule): if 'vid' not in kwargs: return True - template_path = 'triggers/sql/#{0}#'.format(manager.version) + template_path = 'triggers/sql/{0}/#{1}#'.format( + manager.server_type, manager.version) SQL = render_template("/".join( [template_path, 'backend_support.sql']), vid=kwargs['vid'] ) @@ -272,8 +273,8 @@ class TriggerView(PGChildNodeView): kwargs['did'] in self.manager.db_info else 0 # we will set template path for sql scripts - self.template_path = 'triggers/sql/#{0}#'.format( - self.manager.version) + self.template_path = 'triggers/sql/{0}/#{1}#'.format( + self.manager.server_type, self.manager.version) # Store server type self.server_type = self.manager.server_type # We need parent's name eg table name and schema name diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/triggers/tests/test_triggers_delete_multiple.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/triggers/tests/test_triggers_delete_multiple.py index 01c1086c2..17232862b 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/triggers/tests/test_triggers_delete_multiple.py +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/triggers/tests/test_triggers_delete_multiple.py @@ -24,15 +24,15 @@ from regression.python_test_utils import test_utils as utils from . import utils as triggers_utils -class TriggersDeleteTestCase(BaseTestGenerator): +class TriggersDeleteMultipleTestCase(BaseTestGenerator): """This class will delete trigger under table node.""" skip_on_database = ['gpdb'] scenarios = [ - ('Delete trigger Node URL', dict(url='/browser/trigger/obj/')) + ('Delete multiple triggers', dict(url='/browser/trigger/obj/')) ] def setUp(self): - super(TriggersDeleteTestCase, self).setUp() + super(TriggersDeleteMultipleTestCase, self).setUp() self.db_name = parent_node_dict["database"][-1]["db_name"] schema_info = parent_node_dict["schema"][-1] self.server_id = schema_info["server_id"] diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/triggers/tests/test_triggers_put.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/triggers/tests/test_triggers_put.py index 6030a8cfd..ed0d0c352 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/triggers/tests/test_triggers_put.py +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/triggers/tests/test_triggers_put.py @@ -57,7 +57,7 @@ class TriggersUpdateTestCase(BaseTestGenerator): self.function_info = \ trigger_funcs_utils.create_trigger_function_with_trigger( self.server, self.db_name, self.schema_name, self.func_name) - self.trigger_name = "test_trigger_delete_%s" % (str(uuid.uuid4())[1:8]) + self.trigger_name = "test_trigger_update_%s" % (str(uuid.uuid4())[1:8]) self.trigger_id = triggers_utils.create_trigger(self.server, self.db_name, self.schema_name, @@ -71,7 +71,7 @@ class TriggersUpdateTestCase(BaseTestGenerator): self.db_name, self.trigger_name) if not trigger_response: - raise Exception("Could not find the trigger to delete.") + raise Exception("Could not find the trigger to update.") data = {"id": self.trigger_id, "description": "This is test comment." } diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/utils.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/utils.py index 429729200..775f9e22e 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/utils.py +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/utils.py @@ -139,8 +139,12 @@ class BaseTableView(PGChildNodeView, BasePartitionTable): 'indexes/sql', server_type, ver) # Template for trigger node - self.trigger_template_path = compile_template_path( - 'triggers/sql', server_type, ver) + self.trigger_template_path = \ + 'triggers/sql/{0}/#{1}#'.format(server_type, ver) + + # Template for compound trigger node + self.compound_trigger_template_path = \ + 'compound_triggers/sql/{0}/#{1}#'.format(server_type, ver) # Template for rules node self.rules_template_path = 'rules/sql' @@ -1014,8 +1018,6 @@ class BaseTableView(PGChildNodeView, BasePartitionTable): return internal_server_error(errormsg=rset) for row in rset['rows']: - trigger_sql = '' - SQL = render_template("/".join([self.trigger_template_path, 'properties.sql']), tid=tid, trid=row['oid'], @@ -1084,9 +1086,90 @@ class BaseTableView(PGChildNodeView, BasePartitionTable): trigger_sql = re.sub('\n{2,}', '\n\n', trigger_sql) main_sql.append(trigger_sql) + """ + ################################################# + # 4) Reverse engineered sql for COMPOUND TRIGGERS + ################################################# + """ + + if self.manager.server_type == 'ppas' \ + and self.manager.version >= 120000: + SQL = render_template("/".join( + [self.compound_trigger_template_path, 'nodes.sql']), tid=tid) + + status, rset = self.conn.execute_2darray(SQL) + if not status: + return internal_server_error(errormsg=rset) + + for row in rset['rows']: + SQL = render_template("/".join( + [self.compound_trigger_template_path, 'properties.sql']), + tid=tid, trid=row['oid'], + datlastsysoid=self.datlastsysoid) + + status, res = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res) + + if len(res['rows']) == 0: + continue + data = dict(res['rows'][0]) + # Adding parent into data dict, will be using it while + # creating sql + data['schema'] = schema + data['table'] = table + + if len(data['tgattr']) >= 1: + columns = ', '.join(data['tgattr'].split(' ')) + + SQL = render_template("/".join( + [self.compound_trigger_template_path, + 'get_columns.sql']), tid=tid, clist=columns) + + status, rset = self.conn.execute_2darray(SQL) + if not status: + return internal_server_error(errormsg=rset) + # 'tgattr' contains list of columns from table + # used in trigger + columns = [] + + for col_row in rset['rows']: + columns.append(col_row['name']) + + data['columns'] = columns + + data = trigger_definition(data) + sql_header = \ + u"\n-- Compound Trigger: {0}\n\n-- ".format(data['name']) + + sql_header += render_template("/".join( + [self.compound_trigger_template_path, 'delete.sql']), + data=data, conn=self.conn) + + # If the request for new object which do not have did + compound_trigger_sql = render_template("/".join( + [self.compound_trigger_template_path, 'create.sql']), + data=data, conn=self.conn) + + compound_trigger_sql = \ + sql_header + '\n\n' + compound_trigger_sql.strip('\n') + + # If trigger is disabled then add sql code for the same + if not data['is_enable_trigger']: + compound_trigger_sql += '\n\n' + compound_trigger_sql += render_template("/".join( + [self.compound_trigger_template_path, + 'enable_disable_trigger.sql']), + data=data, conn=self.conn) + + # Add into main sql + compound_trigger_sql = \ + re.sub('\n{2,}', '\n\n', compound_trigger_sql) + main_sql.append(compound_trigger_sql) + """ ##################################### - # 4) Reverse engineered sql for RULES + # 5) Reverse engineered sql for RULES ##################################### """ @@ -1118,7 +1201,7 @@ class BaseTableView(PGChildNodeView, BasePartitionTable): """ ########################################## - # 5) Reverse engineered sql for PARTITIONS + # 6) Reverse engineered sql for PARTITIONS ########################################## """ if is_partitioned: diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/__init__.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/__init__.py index 670a7f36e..12442a26c 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/__init__.py +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/__init__.py @@ -892,7 +892,8 @@ class ViewNode(PGChildNodeView, VacuumSettings): SQL_data = '' SQL = render_template("/".join( [self.trigger_temp_path, - 'sql/#{0}#/properties.sql'.format(self.manager.version)]), + 'sql/{0}/#{1}#/properties.sql'.format( + self.manager.server_type, self.manager.version)]), tid=vid) status, data = self.conn.execute_dict(SQL) @@ -902,7 +903,8 @@ class ViewNode(PGChildNodeView, VacuumSettings): for trigger in data['rows']: SQL = render_template("/".join( [self.trigger_temp_path, - 'sql/#{0}#/properties.sql'.format(self.manager.version)]), + 'sql/{0}/#{1}#/properties.sql'.format( + self.manager.server_type, self.manager.version)]), tid=vid, trid=trigger['oid'] ) @@ -932,8 +934,8 @@ class ViewNode(PGChildNodeView, VacuumSettings): # Get trigger function with its schema name SQL = render_template("/".join([ self.trigger_temp_path, - 'sql/#{0}#/get_triggerfunctions.sql'.format( - self.manager.version)]), + 'sql/{0}/#{1}#/get_triggerfunctions.sql'.format( + self.manager.server_type, self.manager.version)]), tgfoid=res_rows['tgfoid'], show_system_objects=self.blueprint.show_system_objects) @@ -956,7 +958,8 @@ class ViewNode(PGChildNodeView, VacuumSettings): SQL = render_template("/".join( [self.trigger_temp_path, - 'sql/#{0}#/create.sql'.format(self.manager.version)]), + 'sql/{0}/#{1}#/create.sql'.format( + self.manager.server_type, self.manager.version)]), data=res_rows, display_comments=True) SQL_data += '\n' SQL_data += SQL diff --git a/web/pgadmin/browser/static/js/menu.js b/web/pgadmin/browser/static/js/menu.js index 3bcba0220..e76c9ed20 100644 --- a/web/pgadmin/browser/static/js/menu.js +++ b/web/pgadmin/browser/static/js/menu.js @@ -78,12 +78,7 @@ define([ data: this.data, }).addClass('dropdown-item'); - if(this.context !== undefined) { - this.is_disabled = this.context.disabled; - } else { - this.is_disabled = this.disabled(node, item); - } - + this.is_disabled = this.disabled(node, item); if (this.icon) { url.append($('', { 'class': this.icon, diff --git a/web/webpack.config.js b/web/webpack.config.js index 5ca3c88a1..254d5b699 100644 --- a/web/webpack.config.js +++ b/web/webpack.config.js @@ -241,7 +241,8 @@ module.exports = { ',pgadmin.node.view' + ',pgadmin.node.mview' + ',pgadmin.node.table' + - ',pgadmin.node.partition', + ',pgadmin.node.partition' + + ',pgadmin.node.compound_trigger', }, }, { test: require.resolve('./node_modules/acitree/js/jquery.aciTree.min'), diff --git a/web/webpack.shim.js b/web/webpack.shim.js index 8f43b2a3d..ca5f72c9d 100644 --- a/web/webpack.shim.js +++ b/web/webpack.shim.js @@ -220,6 +220,7 @@ var webpackShimConfig = { 'pgadmin.node.check_constraint': path.join(__dirname, './pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/check_constraint/static/js/check_constraint'), 'pgadmin.node.collation': path.join(__dirname, './pgadmin/browser/server_groups/servers/databases/schemas/collations/static/js/collation'), 'pgadmin.node.column': path.join(__dirname, './pgadmin/browser/server_groups/servers/databases/schemas/tables/columns/static/js/column'), + 'pgadmin.node.compound_trigger': path.join(__dirname, './pgadmin/browser/server_groups/servers/databases/schemas/tables/compound_triggers/static/js/compound_trigger'), 'pgadmin.node.constraints': path.join(__dirname, './pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/static/js/constraints'), 'pgadmin.node.database': path.join(__dirname, './pgadmin/browser/server_groups/servers/databases/static/js/database'), 'pgadmin.node.domain': path.join(__dirname, './pgadmin/browser/server_groups/servers/databases/schemas/domains/static/js/domain'),