1) Ensure compound triggers should be displayed under Views. Fixes #4638.

2) Ensure Truncate option should be available for Compound Triggers. Fixes #4641.
This commit is contained in:
Akshay Joshi 2019-08-20 09:39:31 +05:30
parent 624f229ce7
commit 0d6f07a035
11 changed files with 259 additions and 12 deletions

View File

@ -34,7 +34,8 @@ Use the fields in the *Events* tab to specify how and when the compound trigger
* 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*.
The supported event types are *INSERT*, *UPDATE*, *DELETE* and *TRUNCATE*.
Views cannot have TRUNCATE triggers.
* 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
@ -46,10 +47,10 @@ Click the *Code* tab to continue.
: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.
Use the *Code* field to specify the code for the five timing events
*BEFORE STATEMENT*, *AFTER STATEMENT*, *BEFORE EACH ROW*, *AFTER EACH ROW*,
*INSTEAD OF EACH ROW* that will be invoked when the compound trigger fires.
Basic template is provided with place holders.
Click the *SQL* tab to continue.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 60 KiB

View File

@ -53,3 +53,5 @@ Bug fixes
| `Issue #4585 <https://redmine.postgresql.org/issues/4585>`_ - Fix double click issue to expand the contents of a cell if the resultset was not editable.
| `Issue #4586 <https://redmine.postgresql.org/issues/4586>`_ - Fix generation of reverse engineered SQL for Rules.
| `Issue #4635 <https://redmine.postgresql.org/issues/4635>`_ - Ensure compound triggers for event should be updated properly.
| `Issue #4638 <https://redmine.postgresql.org/issues/4638>`_ - Ensure compound triggers should be displayed under Views.
| `Issue #4641 <https://redmine.postgresql.org/issues/4641>`_ - Ensure Truncate option should be available for Compound Triggers.

View File

@ -185,7 +185,13 @@ define('pgadmin.node.compound_trigger', [
type: 'int', disabled: true, mode: ['properties'],
},{
id: 'is_enable_trigger', label: gettext('Trigger enabled?'),
type: 'switch', disabled: 'inSchema', mode: ['edit', 'properties'],
type: 'switch', mode: ['edit', 'properties'],
disabled: function() {
if(this.node_info && ('catalog' in this.node_info || 'view' in this.node_info)) {
return true;
}
return false;
},
},{
type: 'nested', control: 'fieldset', mode: ['create','edit', 'properties'],
label: gettext('FOR Events'), group: gettext('Events'), contentClass: 'row',
@ -228,6 +234,23 @@ define('pgadmin.node.compound_trigger', [
return false;
return m.inSchemaWithModelCheck.apply(this, [m]);
},
},{
id: 'evnt_truncate', label: gettext('TRUNCATE'),
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_truncate = m.get('evnt_truncate');
// Views cannot have TRUNCATE triggers.
if ('view' in m.node_info)
return true;
if (!_.isUndefined(evn_truncate) && m.node_info['server']['server_type'] == 'ppas')
return false;
return m.inSchemaWithModelCheck.apply(this, [m]);
},
}],
},{
id: 'whenclause', label: gettext('When'),
@ -337,6 +360,12 @@ define('pgadmin.node.compound_trigger', [
' -- Enter any local declarations here\n' +
'BEGIN\n' +
' -- Enter any required code here\n' +
'END;\n\n' +
'-- INSTEAD OF EACH ROW block. Delete if not required.\n' +
'INSTEAD OF 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
@ -397,11 +426,21 @@ define('pgadmin.node.compound_trigger', [
},
// Check to whether trigger is disable ?
canCreate_with_compound_trigger_enable: function(itemData, item, data) {
var treeData = this.getTreeNodeHierarchy(item);
if ('view' in treeData) {
return false;
}
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) {
var treeData = this.getTreeNodeHierarchy(item);
if ('view' in treeData) {
return false;
}
return itemData.icon === 'icon-compound_trigger' &&
this.canCreate.apply(this, [itemData, item, data]);
},

View File

@ -0,0 +1,16 @@
-- 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 OR TRUNCATE OR UPDATE
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;
END "test_compound_trigger_$%{}[]()&*^!@""'`\/#";

View File

@ -94,6 +94,27 @@
"data": {
"name": "test_compound_trigger_$%{}[]()&*^!@\"'`\\/#"
}
}, {
"type": "create",
"name": "Create compound trigger for all event",
"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;",
"evnt_insert": true,
"evnt_update": true,
"evnt_delete": true,
"evnt_truncate": true
},
"expected_sql_file": "create_for_all_event.sql"
}, {
"type": "delete",
"name": "Drop Compound Trigger",
"endpoint": "NODE-compound_trigger.delete_id",
"data": {
"name": "test_compound_trigger_$%{}[]()&*^!@\"'`\\/#"
}
}
]
}

View File

@ -20,12 +20,28 @@ from pgadmin.browser.server_groups.servers.databases.tests import utils as \
from pgadmin.utils.route import BaseTestGenerator
from regression import parent_node_dict
from regression.python_test_utils import test_utils as utils
from pgadmin.browser.server_groups.servers.databases.schemas.views.tests \
import utils as view_utils
class CompoundTriggersAddTestCase(BaseTestGenerator):
"""This class will add new compound trigger under table node."""
skip_on_database = ['gpdb']
scenarios = [
('Create compound trigger for all events',
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;",
"evnt_insert": True,
"evnt_update": True,
"evnt_delete": True,
"evnt_truncate": True
}
)),
('Create compound trigger for insert and delete',
dict(
url='/browser/compound_trigger/obj/',
@ -75,6 +91,47 @@ class CompoundTriggersAddTestCase(BaseTestGenerator):
"columns": ["id", "name"]
}
)),
('Create compound trigger for truncate',
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;",
"evnt_truncate": True
}
)),
('Create compound trigger for insert delete and update on view',
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;",
"evnt_insert": True,
"evnt_update": True,
"evnt_delete": True,
"evnt_truncate": False
},
on_view=True
)),
('Create compound trigger for instead of each row',
dict(
url='/browser/compound_trigger/obj/',
data={
"prosrc": "var varchar2(20) := 'Global_var';\n\n"
"INSTEAD OF EACH ROW IS\nBEGIN\n "
"DBMS_OUTPUT.PUT_LINE('Instead of: ' || var)"
";\n var := 'INSTEAD OF EACH ROW';\nEND;",
"evnt_insert": True,
"evnt_update": True,
"evnt_delete": True,
"evnt_truncate": False
},
on_view=True
)),
]
def setUp(self):
@ -112,6 +169,14 @@ class CompoundTriggersAddTestCase(BaseTestGenerator):
self.table_id = tables_utils.create_table(self.server, self.db_name,
self.schema_name,
self.table_name)
view_sql = "CREATE OR REPLACE VIEW %s.%s AS SELECT 'Hello World'; " \
"ALTER TABLE %s.%s OWNER TO %s"
self.view_name = \
"view_compound_trigger_%s" % (str(uuid.uuid4())[1:8])
self.view_id = view_utils.create_view(self.server, self.db_name,
self.schema_name,
view_sql,
self.view_name)
def runTest(self):
"""This function will create compound trigger under table node."""
@ -120,10 +185,14 @@ class CompoundTriggersAddTestCase(BaseTestGenerator):
self.data.update({"name": trigger_name})
object_id = self.table_id
if hasattr(self, 'on_view'):
object_id = self.view_id
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),
self.schema_id, object_id),
data=json.dumps(self.data),
content_type='html/json'
)

View File

@ -9,7 +9,10 @@ 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 %}{% 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_update is not defined %}{% if o_data.evnt_update %}
{% 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 %}

View File

@ -187,8 +187,13 @@ define('pgadmin.node.trigger', [
type: 'int', disabled: true, mode: ['properties'],
},{
id: 'is_enable_trigger', label: gettext('Trigger enabled?'),
type: 'switch', disabled: 'inSchema', mode: ['edit', 'properties'],
group: gettext('Definition'),
type: 'switch', mode: ['edit', 'properties'], group: gettext('Definition'),
disabled: function() {
if(this.node_info && ('catalog' in this.node_info || 'view' in this.node_info)) {
return true;
}
return false;
},
},{
id: 'is_row_trigger', label: gettext('Row trigger?'),
type: 'switch', group: gettext('Definition'),
@ -629,11 +634,21 @@ define('pgadmin.node.trigger', [
canCreate: SchemaChildTreeNode.isTreeItemOfChildOfSchema,
// Check to whether trigger is disable ?
canCreate_with_trigger_enable: function(itemData, item, data) {
var treeData = this.getTreeNodeHierarchy(item);
if ('view' in treeData) {
return false;
}
return itemData.icon === 'icon-trigger-bad' &&
this.canCreate.apply(this, [itemData, item, data]);
},
// Check to whether trigger is enable ?
canCreate_with_trigger_disable: function(itemData, item, data) {
var treeData = this.getTreeNodeHierarchy(item);
if ('view' in treeData) {
return false;
}
return itemData.icon === 'icon-trigger' &&
this.canCreate.apply(this, [itemData, item, data]);
},

View File

@ -874,6 +874,83 @@ class ViewNode(PGChildNodeView, VacuumSettings):
SQL_data += SQL
return SQL_data
def get_compound_trigger_sql(self, vid):
"""
Get all compound trigger nodes associated with view node,
generate their sql and render into sql tab
"""
if self.manager.server_type == 'ppas' \
and self.manager.version >= 120000:
from pgadmin.browser.server_groups.servers.databases.schemas.utils\
import trigger_definition
# Define template path
self.ct_trigger_temp_path = 'compound_triggers'
SQL_data = ''
SQL = render_template("/".join(
[self.ct_trigger_temp_path,
'sql/{0}/#{1}#/nodes.sql'.format(
self.manager.server_type, self.manager.version)]),
tid=vid)
status, data = self.conn.execute_dict(SQL)
if not status:
return internal_server_error(errormsg=data)
for trigger in data['rows']:
SQL = render_template("/".join(
[self.ct_trigger_temp_path,
'sql/{0}/#{1}#/properties.sql'.format(
self.manager.server_type, self.manager.version)]),
tid=vid,
trid=trigger['oid']
)
status, res = self.conn.execute_dict(SQL)
if not status:
return internal_server_error(errormsg=res)
if len(res['rows']) == 0:
continue
res_rows = dict(res['rows'][0])
res_rows['table'] = res_rows['relname']
res_rows['schema'] = self.view_schema
if len(res_rows['tgattr']) > 1:
columns = ', '.join(res_rows['tgattr'].split(' '))
SQL = render_template("/".join(
[self.ct_trigger_temp_path,
'sql/{0}/#{1}#/get_columns.sql'.format(
self.manager.server_type,
self.manager.version)]),
tid=trigger['oid'],
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'])
res_rows['columns'] = columns
res_rows = trigger_definition(res_rows)
SQL = render_template("/".join(
[self.ct_trigger_temp_path,
'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
return SQL_data
def get_trigger_sql(self, vid):
"""
Get all trigger nodes associated with view node,
@ -943,7 +1020,8 @@ class ViewNode(PGChildNodeView, VacuumSettings):
if not status:
return internal_server_error(errormsg=result)
# Update the trigger function which we have fetched with schemaname
# Update the trigger function which we have fetched with
# schemaname
if (
'rows' in result and len(result['rows']) > 0 and
'tfunctions' in result['rows'][0]
@ -1083,6 +1161,7 @@ class ViewNode(PGChildNodeView, VacuumSettings):
SQL_data += SQL
SQL_data += self.get_rule_sql(vid)
SQL_data += self.get_trigger_sql(vid)
SQL_data += self.get_compound_trigger_sql(vid)
SQL_data += self.get_index_sql(did, vid)
return ajax_response(response=SQL_data)

View File

@ -13,3 +13,5 @@ from pgadmin.browser.server_groups.servers.databases.schemas.tables.triggers \
import blueprint as triggers_modules
from pgadmin.browser.server_groups.servers.databases.schemas.tables.rules \
import blueprint as rules_modules
from pgadmin.browser.server_groups.servers.databases.schemas.tables.\
compound_triggers import blueprint as compound_trigger_modules